feat(auth): enforce password policy in reset and change flows
This commit is contained in:
@@ -60,6 +60,9 @@ export const en = {
|
|||||||
newPasswordPlaceholder: "New password",
|
newPasswordPlaceholder: "New password",
|
||||||
confirmPasswordPlaceholder: "Confirm password",
|
confirmPasswordPlaceholder: "Confirm password",
|
||||||
passwordMismatch: "The password confirmation does not match.",
|
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",
|
enterPassword: "Enter your password",
|
||||||
verifyNumber: "Verify your number",
|
verifyNumber: "Verify your number",
|
||||||
enterMobileDesc: "Enter your mobile number to continue",
|
enterMobileDesc: "Enter your mobile number to continue",
|
||||||
|
|||||||
@@ -59,6 +59,9 @@ export const fa = {
|
|||||||
newPasswordPlaceholder: "رمز عبور جدید",
|
newPasswordPlaceholder: "رمز عبور جدید",
|
||||||
confirmPasswordPlaceholder: "تکرار رمز عبور",
|
confirmPasswordPlaceholder: "تکرار رمز عبور",
|
||||||
passwordMismatch: "رمز عبور و تکرار آن یکسان نیستند.",
|
passwordMismatch: "رمز عبور و تکرار آن یکسان نیستند.",
|
||||||
|
passwordRequirements:
|
||||||
|
"رمز عبور باید حداقل 8 کاراکتر باشد و حداقل یک حرف کوچک، یک حرف بزرگ، یک عدد و یک نماد داشته باشد.",
|
||||||
|
passwordReuse: "رمز عبور جدید نباید با رمز عبور قبلی یکسان باشد.",
|
||||||
welcome: (title: string = "Qlockifiy") => `به ${title} خوش آمدید`,
|
welcome: (title: string = "Qlockifiy") => `به ${title} خوش آمدید`,
|
||||||
enterPassword: "رمز عبور خود را وارد کنید",
|
enterPassword: "رمز عبور خود را وارد کنید",
|
||||||
verifyNumber: "تایید شماره موبایل",
|
verifyNumber: "تایید شماره موبایل",
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ import { Modal } from "../components/Modal"
|
|||||||
import { Input } from "../components/ui/input"
|
import { Input } from "../components/ui/input"
|
||||||
import { TextAreaInput } from "../components/ui/TextAreaInput"
|
import { TextAreaInput } from "../components/ui/TextAreaInput"
|
||||||
import { AuthPasswordField } from "./auth/AuthPasswordField"
|
import { AuthPasswordField } from "./auth/AuthPasswordField"
|
||||||
|
import { getPasswordValidationMessage } from "./auth/utils"
|
||||||
|
|
||||||
export interface UserProfile {
|
export interface UserProfile {
|
||||||
id?: string;
|
id?: string;
|
||||||
@@ -192,6 +193,17 @@ export default function Profile() {
|
|||||||
return
|
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)
|
setIsSaving(true)
|
||||||
try {
|
try {
|
||||||
await changePassword(
|
await changePassword(
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import { useAuthFlow } from "../../context/AuthFlowContext"
|
|||||||
import { useTranslation } from "../../hooks/useTranslation"
|
import { useTranslation } from "../../hooks/useTranslation"
|
||||||
import { AuthPanel } from "./AuthPanel"
|
import { AuthPanel } from "./AuthPanel"
|
||||||
import { AuthPasswordField } from "./AuthPasswordField"
|
import { AuthPasswordField } from "./AuthPasswordField"
|
||||||
import { getApiErrorMessage } from "./utils"
|
import { getApiErrorMessage, getPasswordValidationMessage } from "./utils"
|
||||||
|
|
||||||
export function ForgotPasswordPasswordPage() {
|
export function ForgotPasswordPasswordPage() {
|
||||||
const navigate = useNavigate()
|
const navigate = useNavigate()
|
||||||
@@ -40,6 +40,12 @@ export function ForgotPasswordPasswordPage() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const passwordValidationMessage = getPasswordValidationMessage(password, t.login)
|
||||||
|
if (passwordValidationMessage) {
|
||||||
|
toast.error(passwordValidationMessage)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
setLoading(true)
|
setLoading(true)
|
||||||
try {
|
try {
|
||||||
await resetPasswordWithOtp(state.forgotPassword.mobile, state.forgotPassword.code, password, confirmation)
|
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 { useTranslation } from "../../hooks/useTranslation"
|
||||||
import { AuthPanel } from "./AuthPanel"
|
import { AuthPanel } from "./AuthPanel"
|
||||||
import { AuthPasswordField } from "./AuthPasswordField"
|
import { AuthPasswordField } from "./AuthPasswordField"
|
||||||
import { completeAuthentication, getApiErrorMessage } from "./utils"
|
import { completeAuthentication, getApiErrorMessage, getPasswordValidationMessage } from "./utils"
|
||||||
|
|
||||||
export function SignupPasswordPage() {
|
export function SignupPasswordPage() {
|
||||||
const navigate = useNavigate()
|
const navigate = useNavigate()
|
||||||
@@ -40,6 +40,12 @@ export function SignupPasswordPage() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const passwordValidationMessage = getPasswordValidationMessage(password, t.login)
|
||||||
|
if (passwordValidationMessage) {
|
||||||
|
toast.error(passwordValidationMessage)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
setLoading(true)
|
setLoading(true)
|
||||||
try {
|
try {
|
||||||
const data = await registerWithOtp(state.signup.mobile, state.signup.code, password, confirmation)
|
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
|
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 = ({
|
export const handleThrottleError = ({
|
||||||
error,
|
error,
|
||||||
cooldownKey,
|
cooldownKey,
|
||||||
|
|||||||
Reference in New Issue
Block a user