Repository: supabase/supabase
File: apps/studio/components/interfaces/SignIn/ResetPasswordForm.tsx (TypeScript)

Code before:
  .max(72, 'Password cannot exceed 72 characters')
  .refine((password) => {
    const hasUppercase = /[A-Z]/.test(password)
    const hasLowercase = /[a-z]/.test(password)
    const hasNumber = /[0-9]/.test(password)
    const hasSpecialChar = /[!@#$%^&*()_+\-=\[\]{};`':"\\|,.<>\/?]/.test(password)
    const isLongEnough = password.length >= 8

    return hasUppercase && hasLowercase && hasNumber && hasSpecialChar && isLongEnough
  }, 'Password must contain at least 8 characters, including uppercase, lowercase, number, and special character')

const passwordSchema = z.object({
  currentPassword: z.string().min(1, 'Current password is required'),
  password: passwordValidation,
})

const recoveryPasswordSchema = z.object({
  currentPassword: z.string().optional(),
  password: passwordValidation,
})

type FormData = z.infer<typeof passwordSchema>

const ResetPasswordForm = () => {
  const router = useRouter()
  const isRecoveryFlow = router.query.type === 'recovery'
  const [showConditions, setShowConditions] = useState(false)
  const [passwordHidden, setPasswordHidden] = useState(true)
  const [currentPasswordHidden, setCurrentPasswordHidden] = useState(true)

  const form = useForm<FormData>({
    resolver: zodResolver(isRecoveryFlow ? recoveryPasswordSchema : passwordSchema),
    defaultValues: {
      password: '',
      currentPassword: '',
    },
    mode: 'onChange',
  })

  const onResetPassword = async (data: FormData) => {
    const toastId = toast.loading('Saving password...')
    const { error } = await auth.updateUser({
      password: data.password,
      ...(isRecoveryFlow ? {} : { currentPassword: data.currentPassword }),
    })

    if (!error) {
      toast.success('Password saved successfully!', { id: toastId })

      // logout all other sessions after changing password
      await auth.signOut({ scope: 'others' })

Review comment (suggestion):
the current password field will flash for a moment if the URL has `type=recovery` - looking into how we can make this better

Code after:
    const isLongEnough = password.length >= 8

    return hasUppercase && hasLowercase && hasNumber && hasSpecialChar && isLongEnough
  }, 'Password must contain at least 8 characters, including uppercase, lowercase, number, and special character')

const passwordSchema = z.object({
  currentPassword: z.string().min(1, 'Current password is required'),
  password: passwordValidation,
})

const recoveryPasswordSchema = z.object({
  currentPassword: z.string().optional(),
  password: passwordValidation,
})

type FormData = z.infer<typeof passwordSchema>

export const ResetPasswordForm = () => {
  const router = useRouter()
  const { type } = useParams()
  const requireCurrentPassword = type === 'change'

  const [showConditions, setShowConditions] = useState(false)
  const [passwordHidden, setPasswordHidden] = useState(true)
  const [currentPasswordHidden, setCurrentPasswordHidden] = useState(true)

  const form = useForm<FormData>({
    resolver: zodResolver(requireCurrentPassword ? passwordSchema : recoveryPasswordSchema),
    defaultValues: { password: '', currentPassword: '' },
    mode: 'onChange',
  })

  const onResetPassword = async (data: FormData) => {
    const toastId = toast.loading('Saving password...')
    const { error } = await auth.updateUser({
      password: data.password,
      ...(requireCurrentPassword ? { current_password: data.currentPassword } : {}),
    })

    if (!error) {
      toast.success('Password saved successfully!', { id: toastId })

      // logout all other sessions after changing password
      await auth.signOut({ scope: 'others' })
      await router.push(getReturnToPath('/organizations'))
    } else {
      toast.error(`Failed to save password: ${error.message}`, { id: toastId })
      captureCriticalError(error, 'reset password')
    }
  }