From 2035b20690f2783eb9869da1950e998f417caee4 Mon Sep 17 00:00:00 2001 From: Manuel <30572287+manuel-rw@users.noreply.github.com> Date: Tue, 1 Aug 2023 17:42:19 +0200 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20Add=20password=20strength=20indicat?= =?UTF-8?q?or=20and=20use=20crypto=20safe=20random=20PWs?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 1 + .../Manage/User/Create/security-step.tsx | 142 ++++++++++++++---- src/server/api/root.ts | 13 +- src/server/api/routers/password.ts | 17 +++ src/validations/user.ts | 12 +- yarn.lock | 8 + 6 files changed, 158 insertions(+), 35 deletions(-) create mode 100644 src/server/api/routers/password.ts diff --git a/package.json b/package.json index 0b4afbf28..ca6af6282 100644 --- a/package.json +++ b/package.json @@ -68,6 +68,7 @@ "fily-publish-gridstack": "^0.0.13", "flag-icons": "^6.9.2", "framer-motion": "^10.0.0", + "generate-password": "^1.7.0", "html-entities": "^2.3.3", "i18next": "^22.5.1", "js-file-download": "^0.4.12", diff --git a/src/components/Manage/User/Create/security-step.tsx b/src/components/Manage/User/Create/security-step.tsx index 31ee8f8dc..afccbacd6 100644 --- a/src/components/Manage/User/Create/security-step.tsx +++ b/src/components/Manage/User/Create/security-step.tsx @@ -1,7 +1,46 @@ -import { Button, Card, Flex, Group, PasswordInput } from '@mantine/core'; +import { + Box, + Button, + Card, + Flex, + Group, + PasswordInput, + Popover, + Progress, + Text, +} from '@mantine/core'; import { useForm, zodResolver } from '@mantine/form'; -import { IconArrowLeft, IconArrowRight, IconDice, IconKey } from '@tabler/icons-react'; +import { + IconArrowLeft, + IconArrowRight, + IconCheck, + IconDice, + IconKey, + IconX, +} from '@tabler/icons-react'; +import { useState } from 'react'; import { z } from 'zod'; +import { api } from '~/utils/api'; +import { passwordSchema } from '~/validations/user'; + +const requirements = [ + { re: /[0-9]/, label: 'Includes number' }, + { re: /[a-z]/, label: 'Includes lowercase letter' }, + { re: /[A-Z]/, label: 'Includes uppercase letter' }, + { re: /[$&+,:;=?@#|'<>.^*()%!-]/, label: 'Includes special symbol' }, +]; + +function getStrength(password: string) { + let multiplier = password.length > 5 ? 0 : 1; + + requirements.forEach((requirement) => { + if (!requirement.re.test(password)) { + multiplier += 1; + } + }); + + return Math.max(100 - (100 / (requirements.length + 1)) * multiplier, 10); +} interface CreateAccountSecurityStepProps { nextStep: ({ password }: { password: string }) => void; @@ -21,31 +60,69 @@ export const CreateAccountSecurityStep = ({ validate: zodResolver(createAccountSecurityStepValidationSchema), }); + const { mutateAsync, isLoading } = api.password.generate.useMutation(); + + const [popoverOpened, setPopoverOpened] = useState(false); + const checks = requirements.map((requirement, index) => ( + + )); + + const strength = getStrength(form.values.password); + const color = strength === 100 ? 'teal' : strength > 50 ? 'yellow' : 'red'; + return ( - - } - style={{ - flexGrow: 1, - }} - label="Password" - variant="filled" - mb="md" - withAsterisk - {...form.getInputProps('password')} - /> - - + + +
setPopoverOpened(true)} + onBlurCapture={() => setPopoverOpened(false)} + > + + } + style={{ + flexGrow: 1, + }} + label="Password" + variant="filled" + mb="md" + withAsterisk + {...form.getInputProps('password')} + /> + + +
+
+ + + 5} + /> + {checks} + +