diff --git a/apps/api/plane/authentication/adapter/base.py b/apps/api/plane/authentication/adapter/base.py index baae95453b5..da8899b3eb4 100644 --- a/apps/api/plane/authentication/adapter/base.py +++ b/apps/api/plane/authentication/adapter/base.py @@ -81,8 +81,8 @@ def validate_password(self, email): results = zxcvbn(self.code) if results["score"] < 3: raise AuthenticationException( - error_code=AUTHENTICATION_ERROR_CODES["INVALID_PASSWORD"], - error_message="INVALID_PASSWORD", + error_code=AUTHENTICATION_ERROR_CODES["PASSWORD_TOO_WEAK"], + error_message="PASSWORD_TOO_WEAK", payload={"email": email}, ) return diff --git a/apps/api/plane/authentication/adapter/error.py b/apps/api/plane/authentication/adapter/error.py index 25a7cf56717..658edcf66c0 100644 --- a/apps/api/plane/authentication/adapter/error.py +++ b/apps/api/plane/authentication/adapter/error.py @@ -9,6 +9,7 @@ "USER_ACCOUNT_DEACTIVATED": 5019, # Password strength "INVALID_PASSWORD": 5020, + "PASSWORD_TOO_WEAK": 5021, "SMTP_NOT_CONFIGURED": 5025, # Sign Up "USER_ALREADY_EXIST": 5030, diff --git a/apps/api/plane/authentication/views/app/password_management.py b/apps/api/plane/authentication/views/app/password_management.py index de0baa71b53..92201d3e1f4 100644 --- a/apps/api/plane/authentication/views/app/password_management.py +++ b/apps/api/plane/authentication/views/app/password_management.py @@ -141,8 +141,8 @@ def post(self, request, uidb64, token): results = zxcvbn(password) if results["score"] < 3: exc = AuthenticationException( - error_code=AUTHENTICATION_ERROR_CODES["INVALID_PASSWORD"], - error_message="INVALID_PASSWORD", + error_code=AUTHENTICATION_ERROR_CODES["PASSWORD_TOO_WEAK"], + error_message="PASSWORD_TOO_WEAK", ) url = urljoin( base_host(request=request, is_app=True), diff --git a/apps/api/plane/authentication/views/common.py b/apps/api/plane/authentication/views/common.py index c5dd1714c5e..9012cf5be8a 100644 --- a/apps/api/plane/authentication/views/common.py +++ b/apps/api/plane/authentication/views/common.py @@ -79,8 +79,8 @@ def post(self, request): results = zxcvbn(new_password) if results["score"] < 3: exc = AuthenticationException( - error_code=AUTHENTICATION_ERROR_CODES["INVALID_NEW_PASSWORD"], - error_message="INVALID_NEW_PASSWORD", + error_code=AUTHENTICATION_ERROR_CODES["PASSWORD_TOO_WEAK"], + error_message="PASSWORD_TOO_WEAK", ) return Response(exc.get_error_dict(), status=status.HTTP_400_BAD_REQUEST) diff --git a/apps/api/plane/authentication/views/space/password_management.py b/apps/api/plane/authentication/views/space/password_management.py index 12cc88f63e7..21d1bb67b36 100644 --- a/apps/api/plane/authentication/views/space/password_management.py +++ b/apps/api/plane/authentication/views/space/password_management.py @@ -135,8 +135,8 @@ def post(self, request, uidb64, token): results = zxcvbn(password) if results["score"] < 3: exc = AuthenticationException( - error_code=AUTHENTICATION_ERROR_CODES["INVALID_PASSWORD"], - error_message="INVALID_PASSWORD", + error_code=AUTHENTICATION_ERROR_CODES["PASSWORD_TOO_WEAK"], + error_message="PASSWORD_TOO_WEAK", ) url = f"{base_host(request=request, is_space=True)}/accounts/reset-password/?{urlencode(exc.get_error_dict())}" # noqa: E501 return HttpResponseRedirect(url) diff --git a/apps/api/plane/license/api/views/admin.py b/apps/api/plane/license/api/views/admin.py index 0d37f4fdc0e..7396d5328f7 100644 --- a/apps/api/plane/license/api/views/admin.py +++ b/apps/api/plane/license/api/views/admin.py @@ -187,8 +187,8 @@ def post(self, request): results = zxcvbn(password) if results["score"] < 3: exc = AuthenticationException( - error_code=AUTHENTICATION_ERROR_CODES["INVALID_ADMIN_PASSWORD"], - error_message="INVALID_ADMIN_PASSWORD", + error_code=AUTHENTICATION_ERROR_CODES["PASSWORD_TOO_WEAK"], + error_message="PASSWORD_TOO_WEAK", payload={ "email": email, "first_name": first_name, diff --git a/apps/web/app/(all)/[workspaceSlug]/(settings)/settings/account/security/page.tsx b/apps/web/app/(all)/[workspaceSlug]/(settings)/settings/account/security/page.tsx index cd03a7ca019..df67ca41bb2 100644 --- a/apps/web/app/(all)/[workspaceSlug]/(settings)/settings/account/security/page.tsx +++ b/apps/web/app/(all)/[workspaceSlug]/(settings)/settings/account/security/page.tsx @@ -13,7 +13,7 @@ import { getPasswordStrength } from "@plane/utils"; import { PageHead } from "@/components/core/page-title"; import { ProfileSettingContentHeader } from "@/components/profile/profile-setting-content-header"; // helpers -import { authErrorHandler } from "@/helpers/authentication.helper"; +import { authErrorHandler, passwordErrors } from "@/helpers/authentication.helper"; import type { EAuthenticationErrorCodes } from "@/helpers/authentication.helper"; // hooks import { useUser } from "@/hooks/store/user"; @@ -53,6 +53,7 @@ function SecurityPage() { control, handleSubmit, watch, + setError, formState: { errors, isSubmitting }, reset, } = useForm({ defaultValues }); @@ -88,12 +89,9 @@ function SecurityPage() { message: t("auth.common.password.toast.change_password.success.message"), }); } catch (error: unknown) { - let errorInfo = undefined; - if (error instanceof Error) { - const err = error as Error & { error_code?: string }; - const code = err.error_code?.toString(); - errorInfo = code ? authErrorHandler(code as EAuthenticationErrorCodes) : undefined; - } + const err = error as Error & { error_code?: string }; + const code = err.error_code?.toString(); + const errorInfo = code ? authErrorHandler(code as EAuthenticationErrorCodes) : undefined; setToast({ type: TOAST_TYPE.ERROR, @@ -101,6 +99,13 @@ function SecurityPage() { message: typeof errorInfo?.message === "string" ? errorInfo.message : t("auth.common.password.toast.error.message"), }); + + if (code && passwordErrors.includes(code as EAuthenticationErrorCodes)) { + setError("new_password", { + type: "manual", + message: errorInfo?.message?.toString() || t("auth.common.password.toast.error.message"), + }); + } } }; @@ -200,6 +205,7 @@ function SecurityPage() { )} {passwordSupport} + {errors.new_password && {errors.new_password.message}} {isNewPasswordSameAsOldPassword && !isPasswordInputFocused && ( {t("new_password_must_be_different_from_old_password")} diff --git a/apps/web/app/(all)/profile/security/page.tsx b/apps/web/app/(all)/profile/security/page.tsx index aad3001fa62..ce5b03ccf08 100644 --- a/apps/web/app/(all)/profile/security/page.tsx +++ b/apps/web/app/(all)/profile/security/page.tsx @@ -14,8 +14,7 @@ import { PageHead } from "@/components/core/page-title"; import { ProfileSettingContentHeader } from "@/components/profile/profile-setting-content-header"; import { ProfileSettingContentWrapper } from "@/components/profile/profile-setting-content-wrapper"; // helpers -import { authErrorHandler } from "@/helpers/authentication.helper"; -import type { EAuthenticationErrorCodes } from "@/helpers/authentication.helper"; +import { authErrorHandler, EAuthenticationErrorCodes, passwordErrors } from "@/helpers/authentication.helper"; // hooks import { useUser } from "@/hooks/store/user"; // services @@ -54,6 +53,7 @@ function SecurityPage() { control, handleSubmit, watch, + setError, formState: { errors, isSubmitting }, reset, } = useForm({ defaultValues }); @@ -98,6 +98,13 @@ function SecurityPage() { message: typeof errorInfo?.message === "string" ? errorInfo.message : t("auth.common.password.toast.error.message"), }); + + if (code && passwordErrors.includes(code as EAuthenticationErrorCodes)) { + setError("new_password", { + type: "manual", + message: errorInfo?.message?.toString() || t("auth.common.password.toast.error.message"), + }); + } } }; @@ -198,6 +205,9 @@ function SecurityPage() { )} {passwordSupport} + {errors.new_password && ( + {errors.new_password.message} + )} {isNewPasswordSameAsOldPassword && !isPasswordInputFocused && ( {t("new_password_must_be_different_from_old_password")} diff --git a/apps/web/helpers/authentication.helper.tsx b/apps/web/helpers/authentication.helper.tsx index 4a493e5eda3..8e2633cb567 100644 --- a/apps/web/helpers/authentication.helper.tsx +++ b/apps/web/helpers/authentication.helper.tsx @@ -41,6 +41,7 @@ export enum EAuthenticationErrorCodes { USER_ACCOUNT_DEACTIVATED = "5019", // Password strength INVALID_PASSWORD = "5020", + PASSWORD_TOO_WEAK = "5021", SMTP_NOT_CONFIGURED = "5025", // Sign Up USER_ALREADY_EXIST = "5030", @@ -101,6 +102,7 @@ export type TAuthErrorInfo = { message: ReactNode; }; +// TODO: move all error messages to translation files const errorCodeMessages: { [key in EAuthenticationErrorCodes]: { title: string; message: (email?: string) => ReactNode }; } = { @@ -137,6 +139,10 @@ const errorCodeMessages: { title: `Invalid password`, message: () => `Invalid password. Please try again.`, }, + [EAuthenticationErrorCodes.PASSWORD_TOO_WEAK]: { + title: `Password too weak`, + message: () => `Password too weak. Please try again.`, + }, [EAuthenticationErrorCodes.SMTP_NOT_CONFIGURED]: { title: `SMTP not configured`, message: () => `SMTP not configured. Please contact your administrator.`, @@ -412,6 +418,7 @@ export const authErrorHandler = (errorCode: EAuthenticationErrorCodes, email?: s EAuthenticationErrorCodes.ADMIN_USER_DOES_NOT_EXIST, EAuthenticationErrorCodes.ADMIN_USER_DEACTIVATED, EAuthenticationErrorCodes.RATE_LIMIT_EXCEEDED, + EAuthenticationErrorCodes.PASSWORD_TOO_WEAK, ]; if (bannerAlertErrorCodes.includes(errorCode)) @@ -424,3 +431,8 @@ export const authErrorHandler = (errorCode: EAuthenticationErrorCodes, email?: s return undefined; }; + +export const passwordErrors = [ + EAuthenticationErrorCodes.PASSWORD_TOO_WEAK, + EAuthenticationErrorCodes.INVALID_NEW_PASSWORD, +]; diff --git a/packages/constants/src/auth/index.ts b/packages/constants/src/auth/index.ts index 2c52598e336..57091df3033 100644 --- a/packages/constants/src/auth/index.ts +++ b/packages/constants/src/auth/index.ts @@ -108,6 +108,7 @@ export enum EAuthErrorCodes { USER_ACCOUNT_DEACTIVATED = "5019", // Password strength INVALID_PASSWORD = "5020", + PASSWORD_TOO_WEAK = "5021", SMTP_NOT_CONFIGURED = "5025", // Sign Up USER_ALREADY_EXIST = "5030",