diff --git a/apps/api/plane/authentication/adapter/base.py b/apps/api/plane/authentication/adapter/base.py index 5707ca700a6..7be6b8f6830 100644 --- a/apps/api/plane/authentication/adapter/base.py +++ b/apps/api/plane/authentication/adapter/base.py @@ -85,8 +85,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 74cb44d26aa..f91565df2e8 100644 --- a/apps/api/plane/authentication/adapter/error.py +++ b/apps/api/plane/authentication/adapter/error.py @@ -13,6 +13,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 33a765134f2..48b54dcccb4 100644 --- a/apps/api/plane/authentication/views/app/password_management.py +++ b/apps/api/plane/authentication/views/app/password_management.py @@ -145,8 +145,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 c36ae48321c..086d6b0d3e2 100644 --- a/apps/api/plane/authentication/views/common.py +++ b/apps/api/plane/authentication/views/common.py @@ -83,8 +83,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 49fe4360c5a..ed6682d74ae 100644 --- a/apps/api/plane/authentication/views/space/password_management.py +++ b/apps/api/plane/authentication/views/space/password_management.py @@ -139,8 +139,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 ba75d52aa2d..6217cc87fa4 100644 --- a/apps/api/plane/license/api/views/admin.py +++ b/apps/api/plane/license/api/views/admin.py @@ -191,8 +191,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/core/components/settings/profile/content/pages/security.tsx b/apps/web/core/components/settings/profile/content/pages/security.tsx index 281da2dbabf..2552c71e877 100644 --- a/apps/web/core/components/settings/profile/content/pages/security.tsx +++ b/apps/web/core/components/settings/profile/content/pages/security.tsx @@ -18,8 +18,7 @@ import { getPasswordStrength } from "@plane/utils"; // components import { ProfileSettingsHeading } from "@/components/settings/profile/heading"; // 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 @@ -58,6 +57,7 @@ export const SecurityProfileSettings = observer(function SecurityProfileSettings control, handleSubmit, watch, + setError, formState: { errors, isSubmitting }, reset, } = useForm({ defaultValues }); @@ -93,11 +93,9 @@ export const SecurityProfileSettings = observer(function SecurityProfileSettings message: t("auth.common.password.toast.change_password.success.message"), }); } catch (error: unknown) { - let errorInfo = undefined; - if (error instanceof Error) { - const code = "error_code" in error ? error.error_code?.toString() : undefined; - 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, @@ -105,6 +103,13 @@ export const SecurityProfileSettings = observer(function SecurityProfileSettings 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"), + }); + } } }; @@ -204,6 +209,9 @@ export const SecurityProfileSettings = observer(function SecurityProfileSettings )} {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 ccbb5680098..2df42773899 100644 --- a/apps/web/helpers/authentication.helper.tsx +++ b/apps/web/helpers/authentication.helper.tsx @@ -47,6 +47,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", @@ -107,6 +108,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 }; } = { @@ -143,6 +145,10 @@ const errorCodeMessages: { title: `Invalid password`, message: () => `Invalid password. Please try again.`, }, + [EAuthenticationErrorCodes.PASSWORD_TOO_WEAK]: { + title: `Password too weak`, + message: () => `Please use a stronger password.`, + }, [EAuthenticationErrorCodes.SMTP_NOT_CONFIGURED]: { title: `SMTP not configured`, message: () => `SMTP not configured. Please contact your administrator.`, @@ -418,6 +424,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)) @@ -430,3 +437,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 6da55498a7f..32a7d5eee64 100644 --- a/packages/constants/src/auth/index.ts +++ b/packages/constants/src/auth/index.ts @@ -114,6 +114,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", diff --git a/packages/i18n/src/locales/ru/translations.ts b/packages/i18n/src/locales/ru/translations.ts index 3952883d6cb..6335c35b768 100644 --- a/packages/i18n/src/locales/ru/translations.ts +++ b/packages/i18n/src/locales/ru/translations.ts @@ -23,6 +23,7 @@ export default { favorites: "Избранное", pro: "Pro", upgrade: "Обновить", + stickies: "Стикеры", }, auth: { common: { @@ -2002,8 +2003,7 @@ export default { automations: { label: "Автоматизация", heading: "Автоматизация", - description: - "Настройте автоматические действия для оптимизации рабочего процесса и сокращения ручных задач.", + description: "Настройте автоматические действия для оптимизации рабочего процесса и сокращения ручных задач.", "auto-archive": { title: "Автоархивация закрытых рабочих элементов", description: "Plane будет автоматически архивировать рабочие элементы, которые были завершены или отменены.", @@ -2921,8 +2921,4 @@ export default { enter_number_of_projects: "Введите количество проектов", pin: "Закрепить", unpin: "Открепить", - sidebar: { - stickies: "Стикеры", - your_work: "Ваша работа", - }, } as const; diff --git a/packages/utils/src/auth.ts b/packages/utils/src/auth.ts index d853bccbec9..fd8b80ad007 100644 --- a/packages/utils/src/auth.ts +++ b/packages/utils/src/auth.ts @@ -99,6 +99,10 @@ const errorCodeMessages: { title: `Invalid password`, message: () => `Invalid password. Please try again.`, }, + [EAuthErrorCodes.PASSWORD_TOO_WEAK]: { + title: `Password too weak`, + message: () => `Please use a stronger password.`, + }, [EAuthErrorCodes.SMTP_NOT_CONFIGURED]: { title: `SMTP not configured`, message: () => `SMTP not configured. Please contact your administrator.`,