Client - add password strength and suggestions
This commit is contained in:
parent
9bb894dc03
commit
29760e57f1
@ -13,6 +13,10 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@tmcw/togeojson": "^4.5.0",
|
"@tmcw/togeojson": "^4.5.0",
|
||||||
"@vue-leaflet/vue-leaflet": "^0.6.1",
|
"@vue-leaflet/vue-leaflet": "^0.6.1",
|
||||||
|
"@zxcvbn-ts/core": "^2.0.0",
|
||||||
|
"@zxcvbn-ts/language-common": "^2.0.0",
|
||||||
|
"@zxcvbn-ts/language-en": "^2.0.0",
|
||||||
|
"@zxcvbn-ts/language-fr": "^1.2.0",
|
||||||
"axios": "^0.26.0",
|
"axios": "^0.26.0",
|
||||||
"chart.js": "^3.7.0",
|
"chart.js": "^3.7.0",
|
||||||
"chartjs-plugin-datalabels": "^2.0.0",
|
"chartjs-plugin-datalabels": "^2.0.0",
|
||||||
|
@ -5,20 +5,23 @@
|
|||||||
:placeholder="placeholder"
|
:placeholder="placeholder"
|
||||||
:required="required"
|
:required="required"
|
||||||
:type="showPassword ? 'text' : 'password'"
|
:type="showPassword ? 'text' : 'password'"
|
||||||
|
v-model="passwordValue"
|
||||||
minlength="8"
|
minlength="8"
|
||||||
@input="updatePassword"
|
@input="updatePassword"
|
||||||
@invalid="invalidPassword"
|
@invalid="invalidPassword"
|
||||||
/>
|
/>
|
||||||
<span class="show-password" @click="togglePassword">
|
<div class="show-password" @click="togglePassword">
|
||||||
{{ $t(`user.${showPassword ? 'HIDE' : 'SHOW'}_PASSWORD`) }}
|
{{ $t(`user.${showPassword ? 'HIDE' : 'SHOW'}_PASSWORD`) }}
|
||||||
</span>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, toRefs, withDefaults } from 'vue'
|
import { Ref, ref, toRefs, watch, withDefaults } from 'vue'
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
disabled?: boolean
|
disabled?: boolean
|
||||||
|
password?: string
|
||||||
placeholder?: string
|
placeholder?: string
|
||||||
required?: boolean
|
required?: boolean
|
||||||
}
|
}
|
||||||
@ -27,8 +30,10 @@
|
|||||||
disabled: false,
|
disabled: false,
|
||||||
required: false,
|
required: false,
|
||||||
})
|
})
|
||||||
const { disabled, placeholder, required } = toRefs(props)
|
const { disabled, password, placeholder, required } = toRefs(props)
|
||||||
const showPassword = ref(false)
|
|
||||||
|
const showPassword: Ref<boolean> = ref(false)
|
||||||
|
const passwordValue: Ref<string> = ref('')
|
||||||
|
|
||||||
const emit = defineEmits(['updatePassword', 'passwordError'])
|
const emit = defineEmits(['updatePassword', 'passwordError'])
|
||||||
|
|
||||||
@ -41,6 +46,15 @@
|
|||||||
function invalidPassword() {
|
function invalidPassword() {
|
||||||
emit('passwordError')
|
emit('passwordError')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => password.value,
|
||||||
|
(newPassword) => {
|
||||||
|
if (newPassword === '') {
|
||||||
|
passwordValue.value = ''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
@ -49,6 +63,7 @@
|
|||||||
.password-input {
|
.password-input {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
|
||||||
.show-password {
|
.show-password {
|
||||||
font-style: italic;
|
font-style: italic;
|
||||||
font-size: 0.85em;
|
font-size: 0.85em;
|
||||||
|
154
fittrackee_client/src/components/Common/PasswordStength.vue
Normal file
154
fittrackee_client/src/components/Common/PasswordStength.vue
Normal file
@ -0,0 +1,154 @@
|
|||||||
|
<template>
|
||||||
|
<div class="password-strength">
|
||||||
|
<input
|
||||||
|
class="password-slider"
|
||||||
|
:class="`strength-${passwordScore}`"
|
||||||
|
:style="{ backgroundSize: backgroundSize }"
|
||||||
|
type="range"
|
||||||
|
:value="passwordScore"
|
||||||
|
min="0"
|
||||||
|
max="4"
|
||||||
|
step="1"
|
||||||
|
/>
|
||||||
|
<div v-if="passwordStrength" class="password-strength-details">
|
||||||
|
<span class="password-strength-value">
|
||||||
|
{{ $t('user.PASSWORD_STRENGTH.LABEL') }}:
|
||||||
|
{{ $t(`user.PASSWORD_STRENGTH.${passwordStrength}`) }}
|
||||||
|
</span>
|
||||||
|
<div class="info-box" v-if="passwordSuggestions.length > 0">
|
||||||
|
<ul class="password-feedback">
|
||||||
|
<li v-for="suggestion in passwordSuggestions" :key="suggestion">
|
||||||
|
{{ $t(`user.PASSWORD_STRENGTH.SUGGESTIONS.${suggestion}`) }}
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { zxcvbn } from '@zxcvbn-ts/core'
|
||||||
|
import {
|
||||||
|
ComputedRef,
|
||||||
|
Ref,
|
||||||
|
computed,
|
||||||
|
ref,
|
||||||
|
onBeforeMount,
|
||||||
|
toRefs,
|
||||||
|
watch,
|
||||||
|
} from 'vue'
|
||||||
|
|
||||||
|
import { ROOT_STORE } from '@/store/constants'
|
||||||
|
import { useStore } from '@/use/useStore'
|
||||||
|
import { getPasswordStrength, setZxcvbnOptions } from '@/utils/password'
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
password: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = defineProps<Props>()
|
||||||
|
const { password } = toRefs(props)
|
||||||
|
|
||||||
|
const store = useStore()
|
||||||
|
const language: ComputedRef<string> = computed(
|
||||||
|
() => store.getters[ROOT_STORE.GETTERS.LANGUAGE]
|
||||||
|
)
|
||||||
|
const passwordScore: Ref<number> = ref(0)
|
||||||
|
const passwordStrength: Ref<string> = ref('')
|
||||||
|
const passwordSuggestions: Ref<string[]> = ref([])
|
||||||
|
const backgroundSize = ref('0% 100%')
|
||||||
|
|
||||||
|
onBeforeMount(async () => await setZxcvbnOptions(language.value))
|
||||||
|
|
||||||
|
function calculatePasswordStrength(password: string) {
|
||||||
|
let zxcvbnResult = zxcvbn(password)
|
||||||
|
passwordScore.value = zxcvbnResult.score
|
||||||
|
passwordStrength.value = getPasswordStrength(passwordScore.value)
|
||||||
|
passwordSuggestions.value = zxcvbnResult.feedback.suggestions
|
||||||
|
backgroundSize.value = (passwordScore.value * 100) / 4 + '% 100%'
|
||||||
|
}
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => language.value,
|
||||||
|
async (newLanguageValue) => {
|
||||||
|
await setZxcvbnOptions(newLanguageValue)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
watch(
|
||||||
|
() => password.value,
|
||||||
|
async (newPassword) => {
|
||||||
|
calculatePasswordStrength(newPassword)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
@import '~@/scss/vars.scss';
|
||||||
|
|
||||||
|
.password-strength {
|
||||||
|
cursor: default;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
|
@mixin slider-background-image($color) {
|
||||||
|
background: var(--password-bg-color);
|
||||||
|
background-image: -webkit-gradient(
|
||||||
|
linear,
|
||||||
|
20% 0%,
|
||||||
|
20% 100%,
|
||||||
|
color-stop(0%, $color),
|
||||||
|
color-stop(100%, $color)
|
||||||
|
);
|
||||||
|
background-image: -webkit-linear-gradient(left, $color 0%, $color 100%);
|
||||||
|
background-image: -moz-linear-gradient(left, $color 0%, $color 100%);
|
||||||
|
background-image: -o-linear-gradient(to right, $color 0%, $color 100%);
|
||||||
|
background-image: linear-gradient(to right, $color 0%, $color 100%);
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
}
|
||||||
|
.password-slider {
|
||||||
|
-webkit-appearance: none;
|
||||||
|
appearance: none;
|
||||||
|
border: none;
|
||||||
|
border-radius: 8px;
|
||||||
|
height: 5px;
|
||||||
|
outline: none;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
.strength-0,
|
||||||
|
.strength-1 {
|
||||||
|
@include slider-background-image(var(--password-color-weak));
|
||||||
|
}
|
||||||
|
.strength-2 {
|
||||||
|
@include slider-background-image(var(--password-color-medium));
|
||||||
|
}
|
||||||
|
.strength-3 {
|
||||||
|
@include slider-background-image(var(--password-color-good));
|
||||||
|
}
|
||||||
|
.strength-4 {
|
||||||
|
@include slider-background-image(var(--password-color-strong));
|
||||||
|
}
|
||||||
|
|
||||||
|
.password-slider::-webkit-slider-thumb,
|
||||||
|
.password-slider::-moz-range-thumb {
|
||||||
|
-webkit-appearance: none;
|
||||||
|
appearance: none;
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.password-strength-details {
|
||||||
|
margin-bottom: $default-margin * 0.5;
|
||||||
|
margin-top: -1 * $default-margin;
|
||||||
|
padding: 0 $default-padding;
|
||||||
|
|
||||||
|
.password-strength-value {
|
||||||
|
font-size: 0.85em;
|
||||||
|
}
|
||||||
|
.info-box {
|
||||||
|
padding: $default-padding * 0.1 $default-padding;
|
||||||
|
.password-feedback {
|
||||||
|
padding-left: $default-padding * 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
@ -51,9 +51,14 @@
|
|||||||
? $t('user.ENTER_PASSWORD')
|
? $t('user.ENTER_PASSWORD')
|
||||||
: $t('user.PASSWORD')
|
: $t('user.PASSWORD')
|
||||||
"
|
"
|
||||||
|
:password="formData.password"
|
||||||
@updatePassword="updatePassword"
|
@updatePassword="updatePassword"
|
||||||
@passwordError="invalidateForm"
|
@passwordError="invalidateForm"
|
||||||
/>
|
/>
|
||||||
|
<PasswordStrength
|
||||||
|
v-if="['reset', 'register'].includes(action)"
|
||||||
|
:password="formData.password"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<button type="submit" :disabled="registration_disabled">
|
<button type="submit" :disabled="registration_disabled">
|
||||||
{{ $t(buttonText) }}
|
{{ $t(buttonText) }}
|
||||||
@ -93,6 +98,7 @@
|
|||||||
import { useRoute } from 'vue-router'
|
import { useRoute } from 'vue-router'
|
||||||
|
|
||||||
import PasswordInput from '@/components/Common/PasswordInput.vue'
|
import PasswordInput from '@/components/Common/PasswordInput.vue'
|
||||||
|
import PasswordStrength from '@/components/Common/PasswordStength.vue'
|
||||||
import { AUTH_USER_STORE, ROOT_STORE } from '@/store/constants'
|
import { AUTH_USER_STORE, ROOT_STORE } from '@/store/constants'
|
||||||
import { TAppConfig } from '@/types/application'
|
import { TAppConfig } from '@/types/application'
|
||||||
import { ILoginRegisterFormData } from '@/types/user'
|
import { ILoginRegisterFormData } from '@/types/user'
|
||||||
|
@ -14,6 +14,29 @@
|
|||||||
"PASSWORD_FORGOTTEN": "Forgot password?",
|
"PASSWORD_FORGOTTEN": "Forgot password?",
|
||||||
"PASSWORD_RESET": "Password reset",
|
"PASSWORD_RESET": "Password reset",
|
||||||
"PASSWORD_SENT_EMAIL_TEXT": "Check your email. If your address is in our database, you'll received an email with a link to reset your password.",
|
"PASSWORD_SENT_EMAIL_TEXT": "Check your email. If your address is in our database, you'll received an email with a link to reset your password.",
|
||||||
|
"PASSWORD_STRENGTH": {
|
||||||
|
"WEAK": "weak",
|
||||||
|
"AVERAGE": "average",
|
||||||
|
"GOOD": "good",
|
||||||
|
"STRONG": "strong",
|
||||||
|
"LABEL": "password strength",
|
||||||
|
"SUGGESTIONS": {
|
||||||
|
"l33t": "Avoid predictable letter substitutions like {'@'} for a.",
|
||||||
|
"reverseWords": "Avoid reversed spellings of common words.",
|
||||||
|
"allUppercase": "Capitalize some, but not all letters.",
|
||||||
|
"capitalization": "Capitalize more than the first letter.",
|
||||||
|
"dates": "Avoid dates and years that are associated with you.",
|
||||||
|
"recentYears": "Avoid recent years.",
|
||||||
|
"associatedYears": "Avoid years that are associated with you.",
|
||||||
|
"sequences": "Avoid common character sequences.",
|
||||||
|
"repeated": "Avoid repeated words and characters.",
|
||||||
|
"longerKeyboardPattern": "Use longer keyboard patterns and change typing direction multiple times.",
|
||||||
|
"anotherWord": "Add more words that are less common.",
|
||||||
|
"useWords": "Use multiple words, but avoid common phrases.",
|
||||||
|
"noNeed": "You can create strong passwords without using symbols, numbers, or uppercase letters.",
|
||||||
|
"pwned": "If you use this password elsewhere, you should change it."
|
||||||
|
}
|
||||||
|
},
|
||||||
"PASSWORD_UPDATED": "Your password have been updated. Click {0} to log in.",
|
"PASSWORD_UPDATED": "Your password have been updated. Click {0} to log in.",
|
||||||
"PROFILE": {
|
"PROFILE": {
|
||||||
"BACK_TO_PROFILE": "Back to profile",
|
"BACK_TO_PROFILE": "Back to profile",
|
||||||
|
@ -14,6 +14,28 @@
|
|||||||
"PASSWORD_FORGOTTEN": "Mot de passe oublié ?",
|
"PASSWORD_FORGOTTEN": "Mot de passe oublié ?",
|
||||||
"PASSWORD_RESET": "Réinitialisation du mot de passe",
|
"PASSWORD_RESET": "Réinitialisation du mot de passe",
|
||||||
"PASSWORD_SENT_EMAIL_TEXT": "Vérifiez votre boite mail. Si vote adresse est dans notre base de données, vous recevrez un email avec un lien pour réinitialiser votre mot de passe.",
|
"PASSWORD_SENT_EMAIL_TEXT": "Vérifiez votre boite mail. Si vote adresse est dans notre base de données, vous recevrez un email avec un lien pour réinitialiser votre mot de passe.",
|
||||||
|
"PASSWORD_STRENGTH": {
|
||||||
|
"WEAK": "faible",
|
||||||
|
"AVERAGE": "moyenne",
|
||||||
|
"GOOD": "bonne",
|
||||||
|
"STRONG": "forte",
|
||||||
|
"LABEL": "robustesse du mot de passe ",
|
||||||
|
"SUGGESTIONS": {
|
||||||
|
"l33t": "Évitez les substitutions de lettres prévisibles comme {'@'} pour a.",
|
||||||
|
"reverseWords": "Évitez les orthographes inversées des mots courants",
|
||||||
|
"allUppercase": "Mettez quelques lettres en majuscules, mais pas toutes.",
|
||||||
|
"capitalization": "Capitalisez mais pas seulement la première lettre.",
|
||||||
|
"dates": "Évitez les dates et les années qui vous sont associées. (ex: date ou année de naissance)",
|
||||||
|
"recentYears": "Évitez les dernières années.",
|
||||||
|
"associatedYears": "Évitez les années qui vous sont associées. (ex: date de naissance)",
|
||||||
|
"sequences": "Évitez les séquences de caractères courantes.",
|
||||||
|
"repeated": "Évitez les mots et les caractères répétés.",
|
||||||
|
"longerKeyboardPattern": "Utilisez des motifs de clavier plus longs et changez de sens de frappe plusieurs fois.",
|
||||||
|
"anotherWord": "Ajoutez des mots moins courants.",
|
||||||
|
"useWords": "Utilisez plusieurs mots, mais évitez les phrases courantes.",
|
||||||
|
"noNeed": "Vous pouvez créer des mots de passe forts sans utiliser de symboles, de chiffres ou de lettres majuscules."
|
||||||
|
}
|
||||||
|
},
|
||||||
"PASSWORD_UPDATED": "Votre mot de passe a été mis à jour. Cliquez {0} pour vous connecter.",
|
"PASSWORD_UPDATED": "Votre mot de passe a été mis à jour. Cliquez {0} pour vous connecter.",
|
||||||
"PROFILE": {
|
"PROFILE": {
|
||||||
"BACK_TO_PROFILE": "Revenir au profil",
|
"BACK_TO_PROFILE": "Revenir au profil",
|
||||||
|
@ -66,6 +66,12 @@
|
|||||||
--cell-heading-bg-color: #eeeeee;
|
--cell-heading-bg-color: #eeeeee;
|
||||||
--cell-heading-color: #696969;
|
--cell-heading-color: #696969;
|
||||||
|
|
||||||
--svg-filter: drop-shadow(10px 10px 10px var(--app-shadow-color))
|
--svg-filter: drop-shadow(10px 10px 10px var(--app-shadow-color));
|
||||||
|
|
||||||
|
--password-bg-color: #d7dadf;
|
||||||
|
--password-color-weak: #e46d6e;
|
||||||
|
--password-color-medium: #f8bc4a;
|
||||||
|
--password-color-good: #acc578;
|
||||||
|
--password-color-strong: #57c255;
|
||||||
|
|
||||||
}
|
}
|
39
fittrackee_client/src/utils/password.ts
Normal file
39
fittrackee_client/src/utils/password.ts
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
import { zxcvbnOptions } from '@zxcvbn-ts/core'
|
||||||
|
|
||||||
|
export const setZxcvbnOptions = async (language: string) => {
|
||||||
|
const zxcvbnCommonPackage = await import(
|
||||||
|
/* webpackChunkName: "password" */ '@zxcvbn-ts/language-common'
|
||||||
|
)
|
||||||
|
const zxcvbnEnPackage = await import(
|
||||||
|
/* webpackChunkName: "password" */ '@zxcvbn-ts/language-en'
|
||||||
|
)
|
||||||
|
const zxcvbnFrPackage = await import(
|
||||||
|
/* webpackChunkName: "password" */ '@zxcvbn-ts/language-fr'
|
||||||
|
)
|
||||||
|
const zxcvbnLangPackages: Record<string, typeof zxcvbnEnPackage> = {
|
||||||
|
en: zxcvbnEnPackage,
|
||||||
|
fr: zxcvbnFrPackage,
|
||||||
|
}
|
||||||
|
const zxcvbnPackage = zxcvbnLangPackages[language]
|
||||||
|
const options = {
|
||||||
|
graphs: zxcvbnCommonPackage.default.adjacencyGraphs,
|
||||||
|
dictionary: {
|
||||||
|
...zxcvbnCommonPackage.default.dictionary,
|
||||||
|
...zxcvbnPackage.default.dictionary,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
zxcvbnOptions.setOptions(options)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getPasswordStrength = (strength: number): string => {
|
||||||
|
switch (strength) {
|
||||||
|
case 2:
|
||||||
|
return 'AVERAGE'
|
||||||
|
case 3:
|
||||||
|
return 'GOOD'
|
||||||
|
case 4:
|
||||||
|
return 'STRONG'
|
||||||
|
default:
|
||||||
|
return 'WEAK'
|
||||||
|
}
|
||||||
|
}
|
@ -2080,6 +2080,28 @@
|
|||||||
resolved "https://registry.yarnpkg.com/@xtuc/long/-/long-4.2.2.tgz#d291c6a4e97989b5c61d9acf396ae4fe133a718d"
|
resolved "https://registry.yarnpkg.com/@xtuc/long/-/long-4.2.2.tgz#d291c6a4e97989b5c61d9acf396ae4fe133a718d"
|
||||||
integrity sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==
|
integrity sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==
|
||||||
|
|
||||||
|
"@zxcvbn-ts/core@^2.0.0":
|
||||||
|
version "2.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/@zxcvbn-ts/core/-/core-2.0.0.tgz#4b6969cd98c6b56ee75bce11c2c9d7ed1168c1db"
|
||||||
|
integrity sha512-j9XY5TQq6fldHQ5BC/3kVNcw9zIg91i7ddeIZzwL8xAq3nqi7gw/YZxPY8Ry4KE4xmcYCiB+6AG6/jHO9uylPg==
|
||||||
|
dependencies:
|
||||||
|
fastest-levenshtein "1.0.12"
|
||||||
|
|
||||||
|
"@zxcvbn-ts/language-common@^2.0.0":
|
||||||
|
version "2.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/@zxcvbn-ts/language-common/-/language-common-2.0.0.tgz#77ee6c1107e116cb74e0e8c80147bc967dd2140c"
|
||||||
|
integrity sha512-RM4PmOev2pRQ1gMf5rjFKvsEb+qYTy+5YZY/g+vy7QibR66TiyD91VoOVaHArcj07wXj0J3eDpsiU+mpB1Oijg==
|
||||||
|
|
||||||
|
"@zxcvbn-ts/language-en@^2.0.0":
|
||||||
|
version "2.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/@zxcvbn-ts/language-en/-/language-en-2.0.0.tgz#454b09578f8713bd204465354565972d8512de85"
|
||||||
|
integrity sha512-ijDtOYeJxBpuoXdTtyXpoOcMRTDjRiABIWX3X7T7XquZ2c6IfrKwXVrvwzVSlRhhPqaCI0tienDeQYG7631eHA==
|
||||||
|
|
||||||
|
"@zxcvbn-ts/language-fr@^1.2.0":
|
||||||
|
version "1.2.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/@zxcvbn-ts/language-fr/-/language-fr-1.2.0.tgz#fb1d5f06d3bdf0b18bab75625a22153fe1dc4347"
|
||||||
|
integrity sha512-+iymwuu+GTlCyJTEhStxqcJfnYvhwIoo6dbXpRq4wzs0mk3uxy79UA6k9lEVy3RXu5VOq2cIpyeLtwqBR4+C7w==
|
||||||
|
|
||||||
abab@^2.0.3, abab@^2.0.5:
|
abab@^2.0.3, abab@^2.0.5:
|
||||||
version "2.0.5"
|
version "2.0.5"
|
||||||
resolved "https://registry.yarnpkg.com/abab/-/abab-2.0.5.tgz#c0b678fb32d60fc1219c784d6a826fe385aeb79a"
|
resolved "https://registry.yarnpkg.com/abab/-/abab-2.0.5.tgz#c0b678fb32d60fc1219c784d6a826fe385aeb79a"
|
||||||
@ -3971,6 +3993,11 @@ fast-levenshtein@^2.0.6, fast-levenshtein@~2.0.6:
|
|||||||
resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917"
|
resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917"
|
||||||
integrity sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=
|
integrity sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=
|
||||||
|
|
||||||
|
fastest-levenshtein@1.0.12:
|
||||||
|
version "1.0.12"
|
||||||
|
resolved "https://registry.yarnpkg.com/fastest-levenshtein/-/fastest-levenshtein-1.0.12.tgz#9990f7d3a88cc5a9ffd1f1745745251700d497e2"
|
||||||
|
integrity sha512-On2N+BpYJ15xIC974QNVuYGMOlEVt4s0EOI3wwMqOmK1fdDY+FN/zltPV8vosq4ad4c/gJ1KHScUn/6AWIgiow==
|
||||||
|
|
||||||
fastq@^1.6.0:
|
fastq@^1.6.0:
|
||||||
version "1.13.0"
|
version "1.13.0"
|
||||||
resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.13.0.tgz#616760f88a7526bdfc596b7cab8c18938c36b98c"
|
resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.13.0.tgz#616760f88a7526bdfc596b7cab8c18938c36b98c"
|
||||||
|
Loading…
Reference in New Issue
Block a user