diff --git a/apiserver/plane/authentication/views/common.py b/apiserver/plane/authentication/views/common.py index cdcf6bc96..7a18072ae 100644 --- a/apiserver/plane/authentication/views/common.py +++ b/apiserver/plane/authentication/views/common.py @@ -44,10 +44,21 @@ class ChangePasswordEndpoint(APIView): def post(self, request): user = User.objects.get(pk=request.user.id) - old_password = request.data.get("old_password", False) + # If the user password is not autoset then we need to check the old passwords + if not user.is_password_autoset: + old_password = request.data.get("old_password", False) + if not old_password: + exc = AuthenticationException( + error_code=AUTHENTICATION_ERROR_CODES["MISSING_PASSWORD"], + error_message="MISSING_PASSWORD", + payload={"error": "Old password is missing"}, + ) + return Response(exc.get_error_dict(), status=status.HTTP_400_BAD_REQUEST) + + # Get the new password new_password = request.data.get("new_password", False) - if not old_password or not new_password: + if not new_password: exc = AuthenticationException( error_code=AUTHENTICATION_ERROR_CODES["MISSING_PASSWORD"], error_message="MISSING_PASSWORD", @@ -55,7 +66,9 @@ class ChangePasswordEndpoint(APIView): ) return Response(exc.get_error_dict(), status=status.HTTP_400_BAD_REQUEST) - if not user.check_password(old_password): + + # If the user password is not autoset then we need to check the old passwords + if not user.is_password_autoset and not user.check_password(old_password): exc = AuthenticationException( error_code=AUTHENTICATION_ERROR_CODES["INCORRECT_OLD_PASSWORD"], error_message="INCORRECT_OLD_PASSWORD", diff --git a/packages/i18n/src/locales/cs/translations.json b/packages/i18n/src/locales/cs/translations.json index 0e2764ec4..51c0aa0e3 100644 --- a/packages/i18n/src/locales/cs/translations.json +++ b/packages/i18n/src/locales/cs/translations.json @@ -499,6 +499,7 @@ "re_generate_key": "Znovu generovat klíč", "export": "Exportovat", "member": "{count, plural, one{# člen} few{# členové} other{# členů}}", + "new_password_must_be_different_from_old_password": "Nové heslo musí být odlišné od starého hesla", "project_view": { "sort_by": { diff --git a/packages/i18n/src/locales/de/translations.json b/packages/i18n/src/locales/de/translations.json index 872fc1746..77ab42515 100644 --- a/packages/i18n/src/locales/de/translations.json +++ b/packages/i18n/src/locales/de/translations.json @@ -499,6 +499,8 @@ "re_generate_key": "Schlüssel neu generieren", "export": "Exportieren", "member": "{count, plural, one{# Mitglied} few{# Mitglieder} other{# Mitglieder}}", + "new_password_must_be_different_from_old_password": "Das neue Passwort muss von dem alten Passwort abweichen", + "project_view": { "sort_by": { "created_at": "Erstellt am", diff --git a/packages/i18n/src/locales/en/translations.json b/packages/i18n/src/locales/en/translations.json index 9191eab12..0be5219be 100644 --- a/packages/i18n/src/locales/en/translations.json +++ b/packages/i18n/src/locales/en/translations.json @@ -331,6 +331,7 @@ "re_generate_key": "Re-generate key", "export": "Export", "member": "{count, plural, one{# member} other{# members}}", + "new_password_must_be_different_from_old_password": "New password must be different from old password", "edited": "edited", "bot": "Bot", diff --git a/packages/i18n/src/locales/es/translations.json b/packages/i18n/src/locales/es/translations.json index 446c49b40..d3a0c1f6c 100644 --- a/packages/i18n/src/locales/es/translations.json +++ b/packages/i18n/src/locales/es/translations.json @@ -503,6 +503,7 @@ "re_generate_key": "Regenerar clave", "export": "Exportar", "member": "{count, plural, one{# miembro} other{# miembros}}", + "new_password_must_be_different_from_old_password": "La nueva contraseña debe ser diferente a la contraseña anterior", "edited": "Modificado", "bot": "Bot", diff --git a/packages/i18n/src/locales/fr/translations.json b/packages/i18n/src/locales/fr/translations.json index dfd3e9310..0cda79656 100644 --- a/packages/i18n/src/locales/fr/translations.json +++ b/packages/i18n/src/locales/fr/translations.json @@ -501,6 +501,7 @@ "re_generate_key": "Régénérer la clé", "export": "Exporter", "member": "{count, plural, one{# membre} other{# membres}}", + "new_password_must_be_different_from_old_password": "Le nouveau mot de passe doit être différent du mot de passe précédent", "edited": "Modifié", "bot": "Bot", diff --git a/packages/i18n/src/locales/id/translations.json b/packages/i18n/src/locales/id/translations.json index 84841d0f1..64338a04f 100644 --- a/packages/i18n/src/locales/id/translations.json +++ b/packages/i18n/src/locales/id/translations.json @@ -501,6 +501,7 @@ "re_generate_key": "Hasilkan kembali kunci", "export": "Ekspor", "member": "{count, plural, one{# anggota} other{# anggota}}", + "new_password_must_be_different_from_old_password": "Kata sandi baru harus berbeda dari kata sandi lama", "project_view": { "sort_by": { diff --git a/packages/i18n/src/locales/it/translations.json b/packages/i18n/src/locales/it/translations.json index 0e1ebfea5..352d7fce4 100644 --- a/packages/i18n/src/locales/it/translations.json +++ b/packages/i18n/src/locales/it/translations.json @@ -500,6 +500,8 @@ "re_generate_key": "Rigenera chiave", "export": "Esporta", "member": "{count, plural, one {# membro} other {# membri}}", + "new_password_must_be_different_from_old_password": "La nuova password deve essere diversa dalla password precedente", + "edited": "Modificato", "bot": "Bot", diff --git a/packages/i18n/src/locales/ja/translations.json b/packages/i18n/src/locales/ja/translations.json index 9997a8a3c..7d0d09175 100644 --- a/packages/i18n/src/locales/ja/translations.json +++ b/packages/i18n/src/locales/ja/translations.json @@ -501,6 +501,7 @@ "re_generate_key": "キーを再生成", "export": "エクスポート", "member": "{count, plural, other{# メンバー}}", + "new_password_must_be_different_from_old_password": "新しいパスワードは古いパスワードと異なる必要があります", "edited": "編集済み", "bot": "ボット", diff --git a/packages/i18n/src/locales/ko/translations.json b/packages/i18n/src/locales/ko/translations.json index 656eb5ded..d6b88df27 100644 --- a/packages/i18n/src/locales/ko/translations.json +++ b/packages/i18n/src/locales/ko/translations.json @@ -501,6 +501,7 @@ "re_generate_key": "키 다시 생성", "export": "내보내기", "member": "{count, plural, one{# 멤버} other{# 멤버}}", + "new_password_must_be_different_from_old_password": "새 비밀번호는 이전 비밀번호와 다르게 설정해야 합니다", "edited": "수정됨", "bot": "봇", diff --git a/packages/i18n/src/locales/pl/translations.json b/packages/i18n/src/locales/pl/translations.json index daef77671..08543fcde 100644 --- a/packages/i18n/src/locales/pl/translations.json +++ b/packages/i18n/src/locales/pl/translations.json @@ -499,6 +499,8 @@ "re_generate_key": "Wygeneruj klucz ponownie", "export": "Eksportuj", "member": "{count, plural, one{# członek} few{# członkowie} other{# członków}}", + "new_password_must_be_different_from_old_password": "Nowe hasło musi być innym niż stare hasło", + "edited": "Edytowano", "bot": "Bot", diff --git a/packages/i18n/src/locales/ro/translations.json b/packages/i18n/src/locales/ro/translations.json index 43ac9eb28..67a642292 100644 --- a/packages/i18n/src/locales/ro/translations.json +++ b/packages/i18n/src/locales/ro/translations.json @@ -501,6 +501,7 @@ "re_generate_key": "Regenerează cheia", "export": "Exportă", "member": "{count, plural, one{# membru} other{# membri}}", + "new_password_must_be_different_from_old_password": "Parola nouă trebuie să fie diferită de parola veche", "project_view": { "sort_by": { diff --git a/packages/i18n/src/locales/ru/translations.json b/packages/i18n/src/locales/ru/translations.json index c12e1ab0e..753d82cd9 100644 --- a/packages/i18n/src/locales/ru/translations.json +++ b/packages/i18n/src/locales/ru/translations.json @@ -499,6 +499,7 @@ "re_generate_key": "Перегенерировать ключ", "export": "Экспорт", "member": "{count, plural, one{# участник} few{# участника} other{# участников}}", + "new_password_must_be_different_from_old_password": "Новое пароль должен отличаться от старого пароля", "edited": "Редактировано", "bot": "Бот", diff --git a/packages/i18n/src/locales/sk/translations.json b/packages/i18n/src/locales/sk/translations.json index a70bc3427..5964af5f4 100644 --- a/packages/i18n/src/locales/sk/translations.json +++ b/packages/i18n/src/locales/sk/translations.json @@ -499,6 +499,7 @@ "re_generate_key": "Znova generovať kľúč", "export": "Exportovať", "member": "{count, plural, one{# člen} few{# členovia} other{# členov}}", + "new_password_must_be_different_from_old_password": "Nové heslo musí byť odlišné od starého hesla", "edited": "Upravené", "bot": "Bot", diff --git a/packages/i18n/src/locales/ua/translations.json b/packages/i18n/src/locales/ua/translations.json index 10f3cc640..3d4048612 100644 --- a/packages/i18n/src/locales/ua/translations.json +++ b/packages/i18n/src/locales/ua/translations.json @@ -499,6 +499,7 @@ "re_generate_key": "Повторно згенерувати ключ", "export": "Експортувати", "member": "{count, plural, one{# учасник} few{# учасники} other{# учасників}}", + "new_password_must_be_different_from_old_password": "Новий пароль повинен бути відмінним від старого пароля", "edited": "Редагувано", "bot": "Бот", diff --git a/packages/i18n/src/locales/zh-CN/translations.json b/packages/i18n/src/locales/zh-CN/translations.json index f83c20909..536c5eb9b 100644 --- a/packages/i18n/src/locales/zh-CN/translations.json +++ b/packages/i18n/src/locales/zh-CN/translations.json @@ -501,6 +501,7 @@ "re_generate_key": "重新生成密钥", "export": "导出", "member": "{count, plural, other{# 成员}}", + "new_password_must_be_different_from_old_password": "新密码必须不同于旧密码", "edited": "已编辑", "bot": "机器人", diff --git a/packages/i18n/src/locales/zh-TW/translations.json b/packages/i18n/src/locales/zh-TW/translations.json index e4bf984ad..c78ad8138 100644 --- a/packages/i18n/src/locales/zh-TW/translations.json +++ b/packages/i18n/src/locales/zh-TW/translations.json @@ -501,6 +501,7 @@ "re_generate_key": "重新產生金鑰", "export": "匯出", "member": "{count, plural, one{# 位成員} other{# 位成員}}", + "new_password_must_be_different_from_old_password": "新密碼必須與舊密碼不同", "edited": "已編輯", "bot": "機器人", diff --git a/web/app/profile/security/page.tsx b/web/app/profile/security/page.tsx index 86098f122..8477d70d9 100644 --- a/web/app/profile/security/page.tsx +++ b/web/app/profile/security/page.tsx @@ -14,9 +14,10 @@ import { ProfileSettingContentHeader, ProfileSettingContentWrapper } from "@/com // helpers import { authErrorHandler } from "@/helpers/authentication.helper"; import { E_PASSWORD_STRENGTH, getPasswordStrength } from "@/helpers/password.helper"; +// hooks +import { useUser } from "@/hooks/store"; // services import { AuthService } from "@/services/auth.service"; -import { UserService } from "@/services/user.service"; export interface FormValues { old_password: string; @@ -30,7 +31,6 @@ const defaultValues: FormValues = { confirm_password: "", }; -const userService = new UserService(); const authService = new AuthService(); const defaultShowPassword = { @@ -40,10 +40,13 @@ const defaultShowPassword = { }; const SecurityPage = observer(() => { + // store + const { data: currentUser, changePassword } = useUser(); // states const [showPassword, setShowPassword] = useState(defaultShowPassword); const [isPasswordInputFocused, setIsPasswordInputFocused] = useState(false); const [isRetryPasswordInputFocused, setIsRetryPasswordInputFocused] = useState(false); + // use form const { control, @@ -56,6 +59,7 @@ const SecurityPage = observer(() => { const oldPassword = watch("old_password"); const password = watch("new_password"); const confirmPassword = watch("confirm_password"); + const oldPasswordRequired = !currentUser?.is_password_autoset; // i18n const { t } = useTranslation(); @@ -70,8 +74,8 @@ const SecurityPage = observer(() => { const csrfToken = await authService.requestCSRFToken().then((data) => data?.csrf_token); if (!csrfToken) throw new Error("csrf token not found"); - await userService.changePassword(csrfToken, { - old_password, + await changePassword(csrfToken, { + ...(oldPasswordRequired && { old_password }), new_password, }); @@ -95,7 +99,7 @@ const SecurityPage = observer(() => { const isButtonDisabled = getPasswordStrength(password) != E_PASSWORD_STRENGTH.STRENGTH_VALID || - oldPassword.trim() === "" || + (oldPasswordRequired && oldPassword.trim() === "") || password.trim() === "" || confirmPassword.trim() === "" || password !== confirmPassword || @@ -115,41 +119,43 @@ const SecurityPage = observer(() => {
-
-

{t("auth.common.password.current_password.label")}

-
- ( - +

{t("auth.common.password.current_password.label")}

+
+ ( + + )} + /> + {showPassword?.oldPassword ? ( + handleShowPassword("oldPassword")} + /> + ) : ( + handleShowPassword("oldPassword")} /> )} - /> - {showPassword?.oldPassword ? ( - handleShowPassword("oldPassword")} - /> - ) : ( - handleShowPassword("oldPassword")} - /> - )} +
+ {errors.old_password && {errors.old_password.message}}
- {errors.old_password && {errors.old_password.message}} -
+ )}

{t("auth.common.password.new_password.label")}

diff --git a/web/core/services/user.service.ts b/web/core/services/user.service.ts index fef39f7e3..c888dfa67 100644 --- a/web/core/services/user.service.ts +++ b/web/core/services/user.service.ts @@ -145,7 +145,7 @@ export class UserService extends APIService { }); } - async changePassword(token: string, data: { old_password: string; new_password: string }): Promise { + async changePassword(token: string, data: { old_password?: string; new_password: string }): Promise { return this.post(`/auth/change-password/`, data, { headers: { "X-CSRFTOKEN": token, diff --git a/web/core/store/user/index.ts b/web/core/store/user/index.ts index 5e60dabcf..fb94c0159 100644 --- a/web/core/store/user/index.ts +++ b/web/core/store/user/index.ts @@ -41,6 +41,10 @@ export interface IUserStore { updateCurrentUser: (data: Partial) => Promise; handleSetPassword: (csrfToken: string, data: { password: string }) => Promise; deactivateAccount: () => Promise; + changePassword: ( + csrfToken: string, + payload: { old_password?: string; new_password: string } + ) => Promise; reset: () => void; signOut: () => Promise; // computed @@ -89,6 +93,7 @@ export class UserStore implements IUserStore { updateCurrentUser: action, handleSetPassword: action, deactivateAccount: action, + changePassword: action, reset: action, signOut: action, // computed @@ -200,6 +205,23 @@ export class UserStore implements IUserStore { } }; + changePassword = async ( + csrfToken: string, + payload: { + old_password?: string; + new_password: string; + } + ): Promise => { + try { + const user = await this.userService.changePassword(csrfToken, payload); + if (this.data) set(this.data, ["is_password_autoset"], false); + return user; + } catch (error) { + console.log(error); + throw error; + } + }; + /** * @description deactivates the current user * @returns {Promise}