From d5c3ea7387e2b052882dd8e75689a104c19bdf2d Mon Sep 17 00:00:00 2001 From: Justin George Date: Mon, 13 Nov 2023 12:53:48 -0800 Subject: [PATCH 1/6] Add password requirements to login page --- packages/app/src/AuthPage.tsx | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/packages/app/src/AuthPage.tsx b/packages/app/src/AuthPage.tsx index 6e5aec8c9..383122b2b 100644 --- a/packages/app/src/AuthPage.tsx +++ b/packages/app/src/AuthPage.tsx @@ -137,6 +137,12 @@ export default function AuthPage({ action }: { action: 'register' | 'login' }) { className="text-start text-muted fs-7.5 mb-1" > Password + {isRegister && ( + <> + (min. 12 characters, 1 uppercase, 1 lowercase, 1 number, 1 + special character) + + )} Date: Tue, 14 Nov 2023 00:31:24 -0800 Subject: [PATCH 2/6] Rough draft of auto-changing password rules --- packages/app/src/AuthPage.tsx | 39 ++++++++--- packages/app/src/PasswordCheck.tsx | 101 +++++++++++++++++++++++++++++ 2 files changed, 132 insertions(+), 8 deletions(-) create mode 100644 packages/app/src/PasswordCheck.tsx diff --git a/packages/app/src/AuthPage.tsx b/packages/app/src/AuthPage.tsx index 383122b2b..1e5ba7122 100644 --- a/packages/app/src/AuthPage.tsx +++ b/packages/app/src/AuthPage.tsx @@ -3,10 +3,11 @@ import { Button, Form } from 'react-bootstrap'; import { NextSeo } from 'next-seo'; import { API_SERVER_URL } from './config'; import { useRouter } from 'next/router'; -import { useEffect } from 'react'; +import { useEffect, useState } from 'react'; import Link from 'next/link'; import cx from 'classnames'; +import { PasswordCheck, CheckOrX } from './PasswordCheck'; import LandingHeader from './LandingHeader'; import * as config from './config'; import api from './api'; @@ -45,6 +46,26 @@ export default function AuthPage({ action }: { action: 'register' | 'login' }) { } }, [installation, isRegister, router]); + const [currentPassword, setCurrentPassword] = useState(''); + const [confirmPassword, setConfirmPassword] = useState(''); + + const updateCurrentPassword = () => { + const val = (document.getElementById('password') as HTMLInputElement).value; + console.log(val); + setCurrentPassword(val); + }; + + const updateConfirmPassword = () => { + const val = (document.getElementById('confirmPassword') as HTMLInputElement) + .value; + console.log(val); + setConfirmPassword(val); + }; + + const confirmPass = (password: string) => { + return currentPassword === confirmPassword; + }; + const onSubmit: SubmitHandler = data => registerPassword.mutate( { @@ -137,12 +158,6 @@ export default function AuthPage({ action }: { action: 'register' | 'login' }) { className="text-start text-muted fs-7.5 mb-1" > Password - {isRegister && ( - <> - (min. 12 characters, 1 uppercase, 1 lowercase, 1 number, 1 - special character) - - )} {}} {...form.password} /> {isRegister && ( @@ -159,15 +175,22 @@ export default function AuthPage({ action }: { action: 'register' | 'login' }) { htmlFor="confirmPassword" className="text-start text-muted fs-7.5 mb-1" > - Confirm Password + + Confirm Password + + )} {isRegister && Object.keys(errors).length > 0 && ( diff --git a/packages/app/src/PasswordCheck.tsx b/packages/app/src/PasswordCheck.tsx new file mode 100644 index 000000000..24c1c6e8b --- /dev/null +++ b/packages/app/src/PasswordCheck.tsx @@ -0,0 +1,101 @@ +import { useEffect, useState } from 'react'; + +const checkLength = (password: string) => password.length >= 12; +const checkOneUpper = (password: string) => /[A-Z]+/.test(password); +const checkOneLower = (password: string) => /[a-z]+/.test(password); +const checkOneNumber = (password: string) => /\d+/.test(password); +const checkOneSpecial = (password: string) => /\W+/.test(password); + +export const PasswordCheck = (password: string | null) => { + password = password ?? ''; + return ( +
    +
  • + + minimum 12 characters + +
  • +
  • + + at least 1 uppercase + +
  • +
  • + + at least 1 lowercase + +
  • +
  • + + at least 1 number + +
  • +
  • + + at least 1 special character + +
  • +
+ ); +}; + +export const CheckOrX = ({ + handler, + password, + children, +}: { + handler: (password: string) => boolean; + password: string; + children: React.ReactNode; +}) => { + const [isValid, setIsValid] = useState(false); + useEffect(() => { + const actualPass = (password['password'] as string) ?? password; + setIsValid(handler(actualPass)); + }, [handler, password]); + return ( + + {isValid ? : } {children} + + ); +}; + +const Check = () => ( + + + +); + +const XShape = () => ( + + + +); From 53698408cba3fc6e5fbfc66a62861071391242dc Mon Sep 17 00:00:00 2001 From: Justin George Date: Tue, 14 Nov 2023 14:29:31 -0800 Subject: [PATCH 3/6] password validation style tweaks and cleanup --- packages/app/src/AuthPage.tsx | 19 ++++++------- packages/app/src/PasswordCheck.tsx | 45 ++++++++++++------------------ 2 files changed, 27 insertions(+), 37 deletions(-) diff --git a/packages/app/src/AuthPage.tsx b/packages/app/src/AuthPage.tsx index 1e5ba7122..edd7f0070 100644 --- a/packages/app/src/AuthPage.tsx +++ b/packages/app/src/AuthPage.tsx @@ -3,7 +3,7 @@ import { Button, Form } from 'react-bootstrap'; import { NextSeo } from 'next-seo'; import { API_SERVER_URL } from './config'; import { useRouter } from 'next/router'; -import { useEffect, useState } from 'react'; +import { KeyboardEventHandler, useState, useEffect } from 'react'; import Link from 'next/link'; import cx from 'classnames'; @@ -18,6 +18,8 @@ type FormData = { confirmPassword: string; }; +type FormControlElement = HTMLInputElement | HTMLTextAreaElement; + export default function AuthPage({ action }: { action: 'register' | 'login' }) { const isRegister = action === 'register'; const { @@ -49,20 +51,17 @@ export default function AuthPage({ action }: { action: 'register' | 'login' }) { const [currentPassword, setCurrentPassword] = useState(''); const [confirmPassword, setConfirmPassword] = useState(''); - const updateCurrentPassword = () => { - const val = (document.getElementById('password') as HTMLInputElement).value; - console.log(val); + const updateCurrentPassword: KeyboardEventHandler = e => { + const val = (e.target as HTMLInputElement).value; setCurrentPassword(val); }; - const updateConfirmPassword = () => { - const val = (document.getElementById('confirmPassword') as HTMLInputElement) - .value; - console.log(val); + const updateConfirmPassword: KeyboardEventHandler = e => { + const val = (e.target as HTMLInputElement).value; setConfirmPassword(val); }; - const confirmPass = (password: string) => { + const confirmPass = () => { return currentPassword === confirmPassword; }; @@ -166,7 +165,7 @@ export default function AuthPage({ action }: { action: 'register' | 'login' }) { className={cx('border-0', { 'mb-3': isRegister, })} - onKeyUp={isRegister ? updateCurrentPassword : () => {}} + onKeyUp={updateCurrentPassword} {...form.password} /> {isRegister && ( diff --git a/packages/app/src/PasswordCheck.tsx b/packages/app/src/PasswordCheck.tsx index 24c1c6e8b..7202d9319 100644 --- a/packages/app/src/PasswordCheck.tsx +++ b/packages/app/src/PasswordCheck.tsx @@ -1,4 +1,4 @@ -import { useEffect, useState } from 'react'; +import { useMemo } from 'react'; const checkLength = (password: string) => password.length >= 12; const checkOneUpper = (password: string) => /[A-Z]+/.test(password); @@ -45,14 +45,19 @@ export const CheckOrX = ({ children, }: { handler: (password: string) => boolean; - password: string; + password: string | { password: string | null }; children: React.ReactNode; }) => { - const [isValid, setIsValid] = useState(false); - useEffect(() => { - const actualPass = (password['password'] as string) ?? password; - setIsValid(handler(actualPass)); - }, [handler, password]); + let actualPassword = ''; + if (typeof password === 'string') { + actualPassword = password; + } else { + actualPassword = password.password ?? ''; + } + const isValid = useMemo( + () => handler(actualPassword), + [handler, actualPassword], + ); return ( {isValid ? : } {children} @@ -63,39 +68,25 @@ export const CheckOrX = ({ const Check = () => ( - + ); const XShape = () => ( - + ); From 2a130a5e3fea261c1930db9df950a08abdf9987f Mon Sep 17 00:00:00 2001 From: Justin George Date: Wed, 15 Nov 2023 09:36:18 -0800 Subject: [PATCH 4/6] Password Check: replace SVG with html icon --- packages/app/src/PasswordCheck.tsx | 26 ++------------------------ 1 file changed, 2 insertions(+), 24 deletions(-) diff --git a/packages/app/src/PasswordCheck.tsx b/packages/app/src/PasswordCheck.tsx index 7202d9319..7aec01062 100644 --- a/packages/app/src/PasswordCheck.tsx +++ b/packages/app/src/PasswordCheck.tsx @@ -65,28 +65,6 @@ export const CheckOrX = ({ ); }; -const Check = () => ( - - - -); +const Check = () => ; -const XShape = () => ( - - - -); +const XShape = () => ; From 64fd30024d61d69507124fc6fc3ef989d9057a52 Mon Sep 17 00:00:00 2001 From: Justin George Date: Fri, 17 Nov 2023 16:04:23 -0800 Subject: [PATCH 5/6] Expand margin below confirm password input Co-authored-by: Mike Shi --- packages/app/src/AuthPage.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/app/src/AuthPage.tsx b/packages/app/src/AuthPage.tsx index edd7f0070..580e40a0c 100644 --- a/packages/app/src/AuthPage.tsx +++ b/packages/app/src/AuthPage.tsx @@ -185,7 +185,7 @@ export default function AuthPage({ action }: { action: 'register' | 'login' }) { data-test-id="form-confirm-password" id="confirmPassword" type="password" - className="border-0" + className="border-0 mb-2" onKeyUp={updateConfirmPassword} {...form.confirmPassword} /> From 3c22f8d7b58c8ff4846b42b7803d4f9d2572b0ac Mon Sep 17 00:00:00 2001 From: Justin George Date: Fri, 17 Nov 2023 16:25:18 -0800 Subject: [PATCH 6/6] Tweak password check to use form watcher and eliminate unordered list --- packages/app/src/AuthPage.tsx | 21 ++++----------------- packages/app/src/PasswordCheck.tsx | 28 ++++++++++++++-------------- 2 files changed, 18 insertions(+), 31 deletions(-) diff --git a/packages/app/src/AuthPage.tsx b/packages/app/src/AuthPage.tsx index 580e40a0c..ca625a494 100644 --- a/packages/app/src/AuthPage.tsx +++ b/packages/app/src/AuthPage.tsx @@ -3,7 +3,7 @@ import { Button, Form } from 'react-bootstrap'; import { NextSeo } from 'next-seo'; import { API_SERVER_URL } from './config'; import { useRouter } from 'next/router'; -import { KeyboardEventHandler, useState, useEffect } from 'react'; +import { useEffect } from 'react'; import Link from 'next/link'; import cx from 'classnames'; @@ -18,8 +18,6 @@ type FormData = { confirmPassword: string; }; -type FormControlElement = HTMLInputElement | HTMLTextAreaElement; - export default function AuthPage({ action }: { action: 'register' | 'login' }) { const isRegister = action === 'register'; const { @@ -27,6 +25,7 @@ export default function AuthPage({ action }: { action: 'register' | 'login' }) { handleSubmit, formState: { errors, isSubmitting }, setError, + watch, } = useForm({ reValidateMode: 'onSubmit', }); @@ -48,18 +47,8 @@ export default function AuthPage({ action }: { action: 'register' | 'login' }) { } }, [installation, isRegister, router]); - const [currentPassword, setCurrentPassword] = useState(''); - const [confirmPassword, setConfirmPassword] = useState(''); - - const updateCurrentPassword: KeyboardEventHandler = e => { - const val = (e.target as HTMLInputElement).value; - setCurrentPassword(val); - }; - - const updateConfirmPassword: KeyboardEventHandler = e => { - const val = (e.target as HTMLInputElement).value; - setConfirmPassword(val); - }; + const currentPassword = watch('password', ''); + const confirmPassword = watch('confirmPassword', ''); const confirmPass = () => { return currentPassword === confirmPassword; @@ -165,7 +154,6 @@ export default function AuthPage({ action }: { action: 'register' | 'login' }) { className={cx('border-0', { 'mb-3': isRegister, })} - onKeyUp={updateCurrentPassword} {...form.password} /> {isRegister && ( @@ -186,7 +174,6 @@ export default function AuthPage({ action }: { action: 'register' | 'login' }) { id="confirmPassword" type="password" className="border-0 mb-2" - onKeyUp={updateConfirmPassword} {...form.confirmPassword} /> diff --git a/packages/app/src/PasswordCheck.tsx b/packages/app/src/PasswordCheck.tsx index 7aec01062..f8a6722a3 100644 --- a/packages/app/src/PasswordCheck.tsx +++ b/packages/app/src/PasswordCheck.tsx @@ -6,36 +6,36 @@ const checkOneLower = (password: string) => /[a-z]+/.test(password); const checkOneNumber = (password: string) => /\d+/.test(password); const checkOneSpecial = (password: string) => /\W+/.test(password); -export const PasswordCheck = (password: string | null) => { - password = password ?? ''; +export const PasswordCheck = (opts: { password: string }) => { + const password = opts.password; return ( -
    -
  • +
    +
    minimum 12 characters -
  • -
  • + +
    at least 1 uppercase -
  • -
  • + +
    at least 1 lowercase -
  • -
  • + +
    at least 1 number -
  • -
  • + +
    at least 1 special character -
  • -
+ + ); };