From a3023d12d253fedb51d1fd6af7a417eeb3041058 Mon Sep 17 00:00:00 2001 From: Jayash Tripathy <76092296+JayashTripathy@users.noreply.github.com> Date: Sat, 10 Jan 2026 00:50:47 +0530 Subject: [PATCH 1/5] refactor(auth): add PASSWORD_TOO_WEAK error code and update related error handling in password change flow --- .../api/plane/authentication/adapter/error.py | 1 + apps/api/plane/authentication/views/common.py | 4 ++-- .../settings/account/security/page.tsx | 22 ++++++++++++------- apps/web/app/(all)/profile/security/page.tsx | 14 ++++++++++-- apps/web/helpers/authentication.helper.tsx | 12 ++++++++++ packages/constants/src/auth/index.ts | 1 + 6 files changed, 42 insertions(+), 12 deletions(-) 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/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/web/app/(all)/[workspaceSlug]/(settings)/settings/account/security/page.tsx b/apps/web/app/(all)/[workspaceSlug]/(settings)/settings/account/security/page.tsx index cd03a7ca019..ad9e414e82b 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,8 +13,8 @@ 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 type { EAuthenticationErrorCodes } from "@/helpers/authentication.helper"; +import { authErrorHandler, passwordErrors } from "@/helpers/authentication.helper"; +import { EAuthenticationErrorCodes } from "@/helpers/authentication.helper"; // hooks import { useUser } from "@/hooks/store/user"; // services @@ -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 (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..ef69c62865c 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 (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", From 3c5320798dbb02194c7a92a237446b5f5acee4b4 Mon Sep 17 00:00:00 2001 From: Jayash Tripathy <76092296+JayashTripathy@users.noreply.github.com> Date: Sat, 10 Jan 2026 00:55:45 +0530 Subject: [PATCH 2/5] fix(auth): update import to use type for EAuthenticationErrorCodes in security page --- .../(settings)/settings/account/security/page.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 ad9e414e82b..d8b9f896236 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 @@ -14,7 +14,7 @@ import { PageHead } from "@/components/core/page-title"; import { ProfileSettingContentHeader } from "@/components/profile/profile-setting-content-header"; // helpers import { authErrorHandler, passwordErrors } from "@/helpers/authentication.helper"; -import { EAuthenticationErrorCodes } from "@/helpers/authentication.helper"; +import type { EAuthenticationErrorCodes } from "@/helpers/authentication.helper"; // hooks import { useUser } from "@/hooks/store/user"; // services From 525329daf9c6b97ff3beba1ea7539c4b14a59f3f Mon Sep 17 00:00:00 2001 From: Jayash Tripathy <76092296+JayashTripathy@users.noreply.github.com> Date: Mon, 12 Jan 2026 13:15:17 +0530 Subject: [PATCH 3/5] Update apps/web/app/(all)/profile/security/page.tsx Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- apps/web/app/(all)/profile/security/page.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/web/app/(all)/profile/security/page.tsx b/apps/web/app/(all)/profile/security/page.tsx index ef69c62865c..ce5b03ccf08 100644 --- a/apps/web/app/(all)/profile/security/page.tsx +++ b/apps/web/app/(all)/profile/security/page.tsx @@ -99,7 +99,7 @@ function SecurityPage() { typeof errorInfo?.message === "string" ? errorInfo.message : t("auth.common.password.toast.error.message"), }); - if (passwordErrors.includes(code as EAuthenticationErrorCodes)) { + if (code && passwordErrors.includes(code as EAuthenticationErrorCodes)) { setError("new_password", { type: "manual", message: errorInfo?.message?.toString() || t("auth.common.password.toast.error.message"), From f6ed77767f9664ee19bb3969a6e6d1a06873e7eb Mon Sep 17 00:00:00 2001 From: Jayash Tripathy <76092296+JayashTripathy@users.noreply.github.com> Date: Mon, 12 Jan 2026 13:15:30 +0530 Subject: [PATCH 4/5] Update apps/web/app/(all)/[workspaceSlug]/(settings)/settings/account/security/page.tsx Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../(settings)/settings/account/security/page.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 d8b9f896236..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 @@ -100,7 +100,7 @@ function SecurityPage() { typeof errorInfo?.message === "string" ? errorInfo.message : t("auth.common.password.toast.error.message"), }); - if (passwordErrors.includes(code as EAuthenticationErrorCodes)) { + if (code && passwordErrors.includes(code as EAuthenticationErrorCodes)) { setError("new_password", { type: "manual", message: errorInfo?.message?.toString() || t("auth.common.password.toast.error.message"), From ec15448c04b02a600826111fce69d12d0582149a Mon Sep 17 00:00:00 2001 From: Jayash Tripathy <76092296+JayashTripathy@users.noreply.github.com> Date: Mon, 12 Jan 2026 13:20:32 +0530 Subject: [PATCH 5/5] refactor: updated auth error exception accross zxcvbn usages --- apps/api/plane/authentication/adapter/base.py | 4 ++-- .../api/plane/authentication/views/app/password_management.py | 4 ++-- .../plane/authentication/views/space/password_management.py | 4 ++-- apps/api/plane/license/api/views/admin.py | 4 ++-- 4 files changed, 8 insertions(+), 8 deletions(-) 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/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/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,