diff --git a/package-lock.json b/package-lock.json index 1a359601..eaeee37c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -37,6 +37,7 @@ "clsx": "^2.1.1", "cmdk": "^1.0.0", "date-fns": "^3.6.0", + "fast-password-entropy": "^1.1.1", "graphql": "^16.8.2", "lodash": "^4.17.21", "lucide-react": "^0.395.0", @@ -70,6 +71,7 @@ "@graphql-codegen/typescript-resolvers": "^4.1.0", "@types/argon2-browser": "^1.18.4", "@types/big.js": "^6.2.2", + "@types/fast-password-entropy": "^1.1.3", "@types/lodash": "^4.17.6", "@types/node": "^20.14.2", "@types/react": "^18.3.3", @@ -5225,6 +5227,12 @@ "@types/node": "*" } }, + "node_modules/@types/fast-password-entropy": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@types/fast-password-entropy/-/fast-password-entropy-1.1.3.tgz", + "integrity": "sha512-vio+mpna1L/nQVAqeolggx428AtODBe1KFUVOStUV75gCCDID9Oz+ElVS5L54kqeooDW2gzT7AfZWlSG+zyTuw==", + "dev": true + }, "node_modules/@types/js-yaml": { "version": "4.0.9", "resolved": "https://registry.npmjs.org/@types/js-yaml/-/js-yaml-4.0.9.tgz", @@ -8255,6 +8263,11 @@ "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", "dev": true }, + "node_modules/fast-password-entropy": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/fast-password-entropy/-/fast-password-entropy-1.1.1.tgz", + "integrity": "sha512-dxm29/BPFrNgyEDygg/lf9c2xQR0vnQhG7+hZjAI39M/3um9fD4xiqG6F0ZjW6bya5m9CI0u6YryHGRtxCGCiw==" + }, "node_modules/fast-querystring": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/fast-querystring/-/fast-querystring-1.1.2.tgz", diff --git a/package.json b/package.json index 3f21d876..9507c151 100644 --- a/package.json +++ b/package.json @@ -43,6 +43,7 @@ "clsx": "^2.1.1", "cmdk": "^1.0.0", "date-fns": "^3.6.0", + "fast-password-entropy": "^1.1.1", "graphql": "^16.8.2", "lodash": "^4.17.21", "lucide-react": "^0.395.0", @@ -76,6 +77,7 @@ "@graphql-codegen/typescript-resolvers": "^4.1.0", "@types/argon2-browser": "^1.18.4", "@types/big.js": "^6.2.2", + "@types/fast-password-entropy": "^1.1.3", "@types/lodash": "^4.17.6", "@types/node": "^20.14.2", "@types/react": "^18.3.3", diff --git a/src/components/SignUpForm.tsx b/src/components/SignUpForm.tsx index f581f0c9..173f701e 100644 --- a/src/components/SignUpForm.tsx +++ b/src/components/SignUpForm.tsx @@ -4,6 +4,7 @@ import { ApolloError, useApolloClient } from '@apollo/client'; import { zodResolver } from '@hookform/resolvers/zod'; import { generateMnemonic } from '@scure/bip39'; import { wordlist } from '@scure/bip39/wordlists/english'; +import stringEntropy from 'fast-password-entropy'; import { Copy, CopyCheck, Eye, EyeOff, Loader2 } from 'lucide-react'; import Link from 'next/link'; import { useEffect, useRef, useState } from 'react'; @@ -30,10 +31,6 @@ import { import { WalletAccountType, WalletType } from '@/graphql/types'; import { toWithError } from '@/utils/async'; import { handleApolloError } from '@/utils/error'; -import { - evaluatePasswordStrength, - MIN_PASSWORD_LENGTH, -} from '@/utils/password'; import { ROUTES } from '@/utils/routes'; import { WorkerMessage, WorkerResponse } from '@/workers/account/types'; @@ -54,14 +51,16 @@ const FormSchema = z email: z.string().email().min(5, { message: 'Invalid email.', }), - password: z.string().min(MIN_PASSWORD_LENGTH, { - message: `Password needs to be at least ${MIN_PASSWORD_LENGTH} characters.`, - }), + password: z.string(), confirm_password: z.string(), password_hint: z.string().optional(), accept_tos_and_pp: z.boolean(), accept_condition_1: z.boolean(), }) + .refine(data => stringEntropy(data.password) >= 100, { + message: 'Password is weak.', + path: ['password'], + }) .refine(data => data.password === data.confirm_password, { message: "Passwords don't match.", path: ['confirm_password'], @@ -102,7 +101,8 @@ export function SignUpForm() { }); const password = form.watch('password', ''); - const strength = evaluatePasswordStrength(password); + + const entropy = stringEntropy(password); const onSubmit = async (data: z.infer) => { if (loading) return; @@ -275,11 +275,10 @@ export function SignUpForm() { - + Important: - Your account cannot be recovered if you forget it! Minimum - length is {MIN_PASSWORD_LENGTH} characters. + Your account cannot be recovered if you forget it! )} diff --git a/src/utils/password.ts b/src/utils/password.ts deleted file mode 100644 index e36aa44b..00000000 --- a/src/utils/password.ts +++ /dev/null @@ -1,34 +0,0 @@ -export const MIN_PASSWORD_LENGTH = 12; - -export const evaluatePasswordStrength = (password: string) => { - if (!password) return; - - let score = 0; - - // Contains lowercase - if (/[a-z]/.test(password)) score += 1; - // Contains uppercase - if (/[A-Z]/.test(password)) score += 1; - - // Contains numbers - if (/\d/.test(password)) score += 1; - // Contains special characters - if (/[^A-Za-z0-9]/.test(password)) score += 1; - - // Check password length - if (password.length < MIN_PASSWORD_LENGTH) score = 0; - - if (password.split(' ').length > 6) score = 4; - - switch (score) { - case 0: - return { title: 'Weak', progress: 0 }; - case 1: - case 2: - return { title: 'Weak', progress: 25 }; - case 3: - return { title: 'Medium', progress: 60 }; - case 4: - return { title: 'Strong', progress: 100 }; - } -};