about summary refs log tree commit diff
diff options
context:
space:
mode:
authorSamuel Newman <mozzius@protonmail.com>2025-02-24 12:44:21 -0800
committerGitHub <noreply@github.com>2025-02-24 12:44:21 -0800
commitae9176c9c2112641c6484ac4b50edfbd94d29661 (patch)
tree14bafaa11ed4a2dfb5a7b2ed722bcf2991896361
parent3d954a00e009f85433e4b5f43d0d1960c8f6c639 (diff)
downloadvoidsky-ae9176c9c2112641c6484ac4b50edfbd94d29661.tar.zst
Basic minimum password requirements, plus field-specific errors (#7811)
* add min password requirement

* add field specific errors

* move email tld check to after other email checks

* add password length check to change password dialog

* Update src/view/com/modals/ChangePassword.tsx

Co-authored-by: Hailey <me@haileyok.com>

* Update src/screens/Signup/StepInfo/index.tsx

Co-authored-by: Hailey <me@haileyok.com>

* fix lint

---------

Co-authored-by: Hailey <me@haileyok.com>
-rw-r--r--src/screens/Signup/StepInfo/index.tsx65
-rw-r--r--src/screens/Signup/state.ts20
-rw-r--r--src/view/com/modals/ChangePassword.tsx17
3 files changed, 79 insertions, 23 deletions
diff --git a/src/screens/Signup/StepInfo/index.tsx b/src/screens/Signup/StepInfo/index.tsx
index 18ba38528..a19a3ad4a 100644
--- a/src/screens/Signup/StepInfo/index.tsx
+++ b/src/screens/Signup/StepInfo/index.tsx
@@ -75,22 +75,6 @@ export function StepInfo({
     const emailChanged = prevEmailValueRef.current !== email
     const password = passwordValueRef.current
 
-    if (emailChanged && tldtsRef.current) {
-      if (isEmailMaybeInvalid(email, tldtsRef.current)) {
-        prevEmailValueRef.current = email
-        setHasWarnedEmail(true)
-        return dispatch({
-          type: 'setError',
-          value: _(
-            msg`It looks like you may have entered your email address incorrectly. Are you sure it's right?`,
-          ),
-        })
-      }
-    } else if (hasWarnedEmail) {
-      setHasWarnedEmail(false)
-    }
-    prevEmailValueRef.current = email
-
     if (!is13(state.dateOfBirth)) {
       return
     }
@@ -99,24 +83,50 @@ export function StepInfo({
       return dispatch({
         type: 'setError',
         value: _(msg`Please enter your invite code.`),
+        field: 'invite-code',
       })
     }
     if (!email) {
       return dispatch({
         type: 'setError',
         value: _(msg`Please enter your email.`),
+        field: 'email',
       })
     }
     if (!EmailValidator.validate(email)) {
       return dispatch({
         type: 'setError',
         value: _(msg`Your email appears to be invalid.`),
+        field: 'email',
       })
     }
+    if (emailChanged && tldtsRef.current) {
+      if (isEmailMaybeInvalid(email, tldtsRef.current)) {
+        prevEmailValueRef.current = email
+        setHasWarnedEmail(true)
+        return dispatch({
+          type: 'setError',
+          value: _(
+            msg`Please double-check that you have entered your email address correctly.`,
+          ),
+        })
+      }
+    } else if (hasWarnedEmail) {
+      setHasWarnedEmail(false)
+    }
+    prevEmailValueRef.current = email
     if (!password) {
       return dispatch({
         type: 'setError',
         value: _(msg`Please choose your password.`),
+        field: 'password',
+      })
+    }
+    if (password.length < 8) {
+      return dispatch({
+        type: 'setError',
+        value: _(msg`Your password must be at least 8 characters long.`),
+        field: 'password',
       })
     }
 
@@ -149,11 +159,17 @@ export function StepInfo({
                 <TextField.LabelText>
                   <Trans>Invite code</Trans>
                 </TextField.LabelText>
-                <TextField.Root>
+                <TextField.Root isInvalid={state.errorField === 'invite-code'}>
                   <TextField.Icon icon={Ticket} />
                   <TextField.Input
                     onChangeText={value => {
                       inviteCodeValueRef.current = value.trim()
+                      if (
+                        state.errorField === 'invite-code' &&
+                        value.trim().length > 0
+                      ) {
+                        dispatch({type: 'clearError'})
+                      }
                     }}
                     label={_(msg`Required for this provider`)}
                     defaultValue={state.inviteCode}
@@ -173,7 +189,7 @@ export function StepInfo({
               <TextField.LabelText>
                 <Trans>Email</Trans>
               </TextField.LabelText>
-              <TextField.Root>
+              <TextField.Root isInvalid={state.errorField === 'email'}>
                 <TextField.Icon icon={Envelope} />
                 <TextField.Input
                   testID="emailInput"
@@ -183,6 +199,13 @@ export function StepInfo({
                     if (hasWarnedEmail) {
                       setHasWarnedEmail(false)
                     }
+                    if (
+                      state.errorField === 'email' &&
+                      value.trim().length > 0 &&
+                      EmailValidator.validate(value.trim())
+                    ) {
+                      dispatch({type: 'clearError'})
+                    }
                   }}
                   label={_(msg`Enter your email address`)}
                   defaultValue={state.email}
@@ -201,13 +224,16 @@ export function StepInfo({
               <TextField.LabelText>
                 <Trans>Password</Trans>
               </TextField.LabelText>
-              <TextField.Root>
+              <TextField.Root isInvalid={state.errorField === 'password'}>
                 <TextField.Icon icon={Lock} />
                 <TextField.Input
                   testID="passwordInput"
                   inputRef={passwordInputRef}
                   onChangeText={value => {
                     passwordValueRef.current = value
+                    if (state.errorField === 'password' && value.length >= 8) {
+                      dispatch({type: 'clearError'})
+                    }
                   }}
                   label={_(msg`Choose your password`)}
                   defaultValue={state.password}
@@ -219,6 +245,7 @@ export function StepInfo({
                   onSubmitEditing={native(() =>
                     birthdateInputRef.current?.focus(),
                   )}
+                  passwordRules="minlength: 8;"
                 />
               </TextField.Root>
             </View>
diff --git a/src/screens/Signup/state.ts b/src/screens/Signup/state.ts
index 4addf3580..3daf36a9b 100644
--- a/src/screens/Signup/state.ts
+++ b/src/screens/Signup/state.ts
@@ -31,6 +31,13 @@ type SubmitTask = {
   mutableProcessed: boolean // OK to mutate assuming it's never read in render.
 }
 
+type ErrorField =
+  | 'invite-code'
+  | 'email'
+  | 'handle'
+  | 'password'
+  | 'date-of-birth'
+
 export type SignupState = {
   hasPrev: boolean
   activeStep: SignupStep
@@ -45,6 +52,7 @@ export type SignupState = {
   handle: string
 
   error: string
+  errorField?: ErrorField
   isLoading: boolean
 
   pendingSubmit: null | SubmitTask
@@ -62,7 +70,8 @@ export type SignupAction =
   | {type: 'setDateOfBirth'; value: Date}
   | {type: 'setInviteCode'; value: string}
   | {type: 'setHandle'; value: string}
-  | {type: 'setError'; value: string}
+  | {type: 'setError'; value: string; field?: ErrorField}
+  | {type: 'clearError'}
   | {type: 'setIsLoading'; value: boolean}
   | {type: 'submit'; task: SubmitTask}
 
@@ -80,6 +89,7 @@ export const initialState: SignupState = {
   inviteCode: '',
 
   error: '',
+  errorField: undefined,
   isLoading: false,
 
   pendingSubmit: null,
@@ -102,6 +112,7 @@ export function reducer(s: SignupState, a: SignupAction): SignupState {
         LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut)
         next.activeStep--
         next.error = ''
+        next.errorField = undefined
       }
       break
     }
@@ -110,6 +121,7 @@ export function reducer(s: SignupState, a: SignupAction): SignupState {
         LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut)
         next.activeStep++
         next.error = ''
+        next.errorField = undefined
       }
       break
     }
@@ -156,6 +168,12 @@ export function reducer(s: SignupState, a: SignupAction): SignupState {
     }
     case 'setError': {
       next.error = a.value
+      next.errorField = a.field
+      break
+    }
+    case 'clearError': {
+      next.error = ''
+      next.errorField = undefined
       break
     }
     case 'submit': {
diff --git a/src/view/com/modals/ChangePassword.tsx b/src/view/com/modals/ChangePassword.tsx
index d68b4e453..9b96e7db0 100644
--- a/src/view/com/modals/ChangePassword.tsx
+++ b/src/view/com/modals/ChangePassword.tsx
@@ -81,8 +81,7 @@ export function Component() {
 
   const onChangePassword = async () => {
     const formattedCode = checkAndFormatResetCode(resetCode)
-    // TODO Better password strength check
-    if (!formattedCode || !newPassword) {
+    if (!formattedCode) {
       setError(
         _(
           msg`You have entered an invalid code. It should look like XXXXX-XXXXX.`,
@@ -90,6 +89,16 @@ export function Component() {
       )
       return
     }
+    if (!newPassword) {
+      setError(
+        _(msg`Please enter a password. It must be at least 8 characters long.`),
+      )
+      return
+    }
+    if (newPassword.length < 8) {
+      setError(_(msg`Password must be at least 8 characters long.`))
+      return
+    }
 
     setError('')
     setIsProcessing(true)
@@ -104,7 +113,9 @@ export function Component() {
       logger.warn('Failed to set new password', {error: e})
       if (isNetworkError(e)) {
         setError(
-          'Unable to contact your service. Please check your Internet connection.',
+          _(
+            msg`Unable to contact your service. Please check your Internet connection.`,
+          ),
         )
       } else {
         setError(cleanError(errMsg))