feat(auth): enforce password policy in reset and change flows
This commit is contained in:
@@ -56,11 +56,14 @@ export const en = {
|
||||
continueToResetPassword: "Continue to reset password",
|
||||
resetPasswordTitle: "Choose a new password",
|
||||
resetPasswordDescription: "Set a new password for your account and confirm it.",
|
||||
resetPasswordCta: "Reset password",
|
||||
newPasswordPlaceholder: "New password",
|
||||
confirmPasswordPlaceholder: "Confirm password",
|
||||
passwordMismatch: "The password confirmation does not match.",
|
||||
enterPassword: "Enter your password",
|
||||
resetPasswordCta: "Reset password",
|
||||
newPasswordPlaceholder: "New password",
|
||||
confirmPasswordPlaceholder: "Confirm password",
|
||||
passwordMismatch: "The password confirmation does not match.",
|
||||
passwordRequirements:
|
||||
"Password must be at least 8 characters and include at least one lowercase letter, one uppercase letter, one digit, and one symbol.",
|
||||
passwordReuse: "New password must be different from your previous password.",
|
||||
enterPassword: "Enter your password",
|
||||
verifyNumber: "Verify your number",
|
||||
enterMobileDesc: "Enter your mobile number to continue",
|
||||
signInDesc: "Sign in using your account password",
|
||||
|
||||
@@ -55,11 +55,14 @@ export const fa = {
|
||||
continueToResetPassword: "ادامه برای تعیین رمز جدید",
|
||||
resetPasswordTitle: "انتخاب رمز عبور جدید",
|
||||
resetPasswordDescription: "رمز عبور جدید خود را وارد کنید و آن را تایید کنید.",
|
||||
resetPasswordCta: "تغییر رمز عبور",
|
||||
newPasswordPlaceholder: "رمز عبور جدید",
|
||||
confirmPasswordPlaceholder: "تکرار رمز عبور",
|
||||
passwordMismatch: "رمز عبور و تکرار آن یکسان نیستند.",
|
||||
welcome: (title: string = "Qlockifiy") => `به ${title} خوش آمدید`,
|
||||
resetPasswordCta: "تغییر رمز عبور",
|
||||
newPasswordPlaceholder: "رمز عبور جدید",
|
||||
confirmPasswordPlaceholder: "تکرار رمز عبور",
|
||||
passwordMismatch: "رمز عبور و تکرار آن یکسان نیستند.",
|
||||
passwordRequirements:
|
||||
"رمز عبور باید حداقل 8 کاراکتر باشد و حداقل یک حرف کوچک، یک حرف بزرگ، یک عدد و یک نماد داشته باشد.",
|
||||
passwordReuse: "رمز عبور جدید نباید با رمز عبور قبلی یکسان باشد.",
|
||||
welcome: (title: string = "Qlockifiy") => `به ${title} خوش آمدید`,
|
||||
enterPassword: "رمز عبور خود را وارد کنید",
|
||||
verifyNumber: "تایید شماره موبایل",
|
||||
enterMobileDesc: "برای ادامه، شماره موبایل خود را وارد کنید",
|
||||
|
||||
@@ -12,10 +12,11 @@ import { Button } from "../components/ui/button"
|
||||
import { Camera, Edit2, Trash2, User as UserIcon, UploadCloud, X, Check } from "lucide-react"
|
||||
import JalaliDatePicker from "../components/ui/JalaliDatePicker"
|
||||
import { toast } from "sonner"
|
||||
import { Modal } from "../components/Modal"
|
||||
import { Input } from "../components/ui/input"
|
||||
import { TextAreaInput } from "../components/ui/TextAreaInput"
|
||||
import { AuthPasswordField } from "./auth/AuthPasswordField"
|
||||
import { Modal } from "../components/Modal"
|
||||
import { Input } from "../components/ui/input"
|
||||
import { TextAreaInput } from "../components/ui/TextAreaInput"
|
||||
import { AuthPasswordField } from "./auth/AuthPasswordField"
|
||||
import { getPasswordValidationMessage } from "./auth/utils"
|
||||
|
||||
export interface UserProfile {
|
||||
id?: string;
|
||||
@@ -187,12 +188,23 @@ export default function Profile() {
|
||||
return
|
||||
}
|
||||
|
||||
if (passwordForm.newPassword !== passwordForm.confirmPassword) {
|
||||
toast.error(t.login.passwordMismatch)
|
||||
return
|
||||
}
|
||||
|
||||
setIsSaving(true)
|
||||
if (passwordForm.newPassword !== passwordForm.confirmPassword) {
|
||||
toast.error(t.login.passwordMismatch)
|
||||
return
|
||||
}
|
||||
|
||||
const passwordValidationMessage = getPasswordValidationMessage(passwordForm.newPassword, t.login)
|
||||
if (passwordValidationMessage) {
|
||||
toast.error(passwordValidationMessage)
|
||||
return
|
||||
}
|
||||
|
||||
if (passwordForm.currentPassword === passwordForm.newPassword) {
|
||||
toast.error(t.login.passwordReuse)
|
||||
return
|
||||
}
|
||||
|
||||
setIsSaving(true)
|
||||
try {
|
||||
await changePassword(
|
||||
passwordForm.currentPassword,
|
||||
|
||||
@@ -9,7 +9,7 @@ import { useAuthFlow } from "../../context/AuthFlowContext"
|
||||
import { useTranslation } from "../../hooks/useTranslation"
|
||||
import { AuthPanel } from "./AuthPanel"
|
||||
import { AuthPasswordField } from "./AuthPasswordField"
|
||||
import { getApiErrorMessage } from "./utils"
|
||||
import { getApiErrorMessage, getPasswordValidationMessage } from "./utils"
|
||||
|
||||
export function ForgotPasswordPasswordPage() {
|
||||
const navigate = useNavigate()
|
||||
@@ -40,6 +40,12 @@ export function ForgotPasswordPasswordPage() {
|
||||
return
|
||||
}
|
||||
|
||||
const passwordValidationMessage = getPasswordValidationMessage(password, t.login)
|
||||
if (passwordValidationMessage) {
|
||||
toast.error(passwordValidationMessage)
|
||||
return
|
||||
}
|
||||
|
||||
setLoading(true)
|
||||
try {
|
||||
await resetPasswordWithOtp(state.forgotPassword.mobile, state.forgotPassword.code, password, confirmation)
|
||||
|
||||
@@ -9,7 +9,7 @@ import { useAuthFlow } from "../../context/AuthFlowContext"
|
||||
import { useTranslation } from "../../hooks/useTranslation"
|
||||
import { AuthPanel } from "./AuthPanel"
|
||||
import { AuthPasswordField } from "./AuthPasswordField"
|
||||
import { completeAuthentication, getApiErrorMessage } from "./utils"
|
||||
import { completeAuthentication, getApiErrorMessage, getPasswordValidationMessage } from "./utils"
|
||||
|
||||
export function SignupPasswordPage() {
|
||||
const navigate = useNavigate()
|
||||
@@ -40,6 +40,12 @@ export function SignupPasswordPage() {
|
||||
return
|
||||
}
|
||||
|
||||
const passwordValidationMessage = getPasswordValidationMessage(password, t.login)
|
||||
if (passwordValidationMessage) {
|
||||
toast.error(passwordValidationMessage)
|
||||
return
|
||||
}
|
||||
|
||||
setLoading(true)
|
||||
try {
|
||||
const data = await registerWithOtp(state.signup.mobile, state.signup.code, password, confirmation)
|
||||
|
||||
@@ -29,6 +29,24 @@ export const getApiErrorMessage = (error: unknown, fallbackMessage: string) => {
|
||||
return fallbackMessage
|
||||
}
|
||||
|
||||
export const getPasswordValidationMessage = (
|
||||
password: string,
|
||||
copy: {
|
||||
passwordRequirements: string
|
||||
},
|
||||
) => {
|
||||
const hasLowercase = /[a-z]/.test(password)
|
||||
const hasUppercase = /[A-Z]/.test(password)
|
||||
const hasDigit = /\d/.test(password)
|
||||
const hasSymbol = /[^A-Za-z0-9]/.test(password)
|
||||
|
||||
if (password.length < 8 || !hasLowercase || !hasUppercase || !hasDigit || !hasSymbol) {
|
||||
return copy.passwordRequirements
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
export const handleThrottleError = ({
|
||||
error,
|
||||
cooldownKey,
|
||||
|
||||
Reference in New Issue
Block a user