[WEB-3673] fix: password change form (#6839)

* fix: change password

* fix: added store action for change password

* fix: type

* fix: store refactor
This commit is contained in:
Akshita Goyal 2025-03-28 13:35:42 +05:30 committed by GitHub
parent 99dba80d19
commit e8779511ad
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
20 changed files with 101 additions and 41 deletions

View file

@ -44,10 +44,21 @@ class ChangePasswordEndpoint(APIView):
def post(self, request): def post(self, request):
user = User.objects.get(pk=request.user.id) 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) new_password = request.data.get("new_password", False)
if not old_password or not new_password: if not new_password:
exc = AuthenticationException( exc = AuthenticationException(
error_code=AUTHENTICATION_ERROR_CODES["MISSING_PASSWORD"], error_code=AUTHENTICATION_ERROR_CODES["MISSING_PASSWORD"],
error_message="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) 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( exc = AuthenticationException(
error_code=AUTHENTICATION_ERROR_CODES["INCORRECT_OLD_PASSWORD"], error_code=AUTHENTICATION_ERROR_CODES["INCORRECT_OLD_PASSWORD"],
error_message="INCORRECT_OLD_PASSWORD", error_message="INCORRECT_OLD_PASSWORD",

View file

@ -499,6 +499,7 @@
"re_generate_key": "Znovu generovat klíč", "re_generate_key": "Znovu generovat klíč",
"export": "Exportovat", "export": "Exportovat",
"member": "{count, plural, one{# člen} few{# členové} other{# členů}}", "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": { "project_view": {
"sort_by": { "sort_by": {

View file

@ -499,6 +499,8 @@
"re_generate_key": "Schlüssel neu generieren", "re_generate_key": "Schlüssel neu generieren",
"export": "Exportieren", "export": "Exportieren",
"member": "{count, plural, one{# Mitglied} few{# Mitglieder} other{# Mitglieder}}", "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": { "project_view": {
"sort_by": { "sort_by": {
"created_at": "Erstellt am", "created_at": "Erstellt am",

View file

@ -331,6 +331,7 @@
"re_generate_key": "Re-generate key", "re_generate_key": "Re-generate key",
"export": "Export", "export": "Export",
"member": "{count, plural, one{# member} other{# members}}", "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", "edited": "edited",
"bot": "Bot", "bot": "Bot",

View file

@ -503,6 +503,7 @@
"re_generate_key": "Regenerar clave", "re_generate_key": "Regenerar clave",
"export": "Exportar", "export": "Exportar",
"member": "{count, plural, one{# miembro} other{# miembros}}", "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", "edited": "Modificado",
"bot": "Bot", "bot": "Bot",

View file

@ -501,6 +501,7 @@
"re_generate_key": "Régénérer la clé", "re_generate_key": "Régénérer la clé",
"export": "Exporter", "export": "Exporter",
"member": "{count, plural, one{# membre} other{# membres}}", "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é", "edited": "Modifié",
"bot": "Bot", "bot": "Bot",

View file

@ -501,6 +501,7 @@
"re_generate_key": "Hasilkan kembali kunci", "re_generate_key": "Hasilkan kembali kunci",
"export": "Ekspor", "export": "Ekspor",
"member": "{count, plural, one{# anggota} other{# anggota}}", "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": { "project_view": {
"sort_by": { "sort_by": {

View file

@ -500,6 +500,8 @@
"re_generate_key": "Rigenera chiave", "re_generate_key": "Rigenera chiave",
"export": "Esporta", "export": "Esporta",
"member": "{count, plural, one {# membro} other {# membri}}", "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", "edited": "Modificato",
"bot": "Bot", "bot": "Bot",

View file

@ -501,6 +501,7 @@
"re_generate_key": "キーを再生成", "re_generate_key": "キーを再生成",
"export": "エクスポート", "export": "エクスポート",
"member": "{count, plural, other{# メンバー}}", "member": "{count, plural, other{# メンバー}}",
"new_password_must_be_different_from_old_password": "新しいパスワードは古いパスワードと異なる必要があります",
"edited": "編集済み", "edited": "編集済み",
"bot": "ボット", "bot": "ボット",

View file

@ -501,6 +501,7 @@
"re_generate_key": "키 다시 생성", "re_generate_key": "키 다시 생성",
"export": "내보내기", "export": "내보내기",
"member": "{count, plural, one{# 멤버} other{# 멤버}}", "member": "{count, plural, one{# 멤버} other{# 멤버}}",
"new_password_must_be_different_from_old_password": "새 비밀번호는 이전 비밀번호와 다르게 설정해야 합니다",
"edited": "수정됨", "edited": "수정됨",
"bot": "봇", "bot": "봇",

View file

@ -499,6 +499,8 @@
"re_generate_key": "Wygeneruj klucz ponownie", "re_generate_key": "Wygeneruj klucz ponownie",
"export": "Eksportuj", "export": "Eksportuj",
"member": "{count, plural, one{# członek} few{# członkowie} other{# członków}}", "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", "edited": "Edytowano",
"bot": "Bot", "bot": "Bot",

View file

@ -501,6 +501,7 @@
"re_generate_key": "Regenerează cheia", "re_generate_key": "Regenerează cheia",
"export": "Exportă", "export": "Exportă",
"member": "{count, plural, one{# membru} other{# membri}}", "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": { "project_view": {
"sort_by": { "sort_by": {

View file

@ -499,6 +499,7 @@
"re_generate_key": "Перегенерировать ключ", "re_generate_key": "Перегенерировать ключ",
"export": "Экспорт", "export": "Экспорт",
"member": "{count, plural, one{# участник} few{# участника} other{# участников}}", "member": "{count, plural, one{# участник} few{# участника} other{# участников}}",
"new_password_must_be_different_from_old_password": "Новое пароль должен отличаться от старого пароля",
"edited": "Редактировано", "edited": "Редактировано",
"bot": "Бот", "bot": "Бот",

View file

@ -499,6 +499,7 @@
"re_generate_key": "Znova generovať kľúč", "re_generate_key": "Znova generovať kľúč",
"export": "Exportovať", "export": "Exportovať",
"member": "{count, plural, one{# člen} few{# členovia} other{# členov}}", "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é", "edited": "Upravené",
"bot": "Bot", "bot": "Bot",

View file

@ -499,6 +499,7 @@
"re_generate_key": "Повторно згенерувати ключ", "re_generate_key": "Повторно згенерувати ключ",
"export": "Експортувати", "export": "Експортувати",
"member": "{count, plural, one{# учасник} few{# учасники} other{# учасників}}", "member": "{count, plural, one{# учасник} few{# учасники} other{# учасників}}",
"new_password_must_be_different_from_old_password": "Новий пароль повинен бути відмінним від старого пароля",
"edited": "Редагувано", "edited": "Редагувано",
"bot": "Бот", "bot": "Бот",

View file

@ -501,6 +501,7 @@
"re_generate_key": "重新生成密钥", "re_generate_key": "重新生成密钥",
"export": "导出", "export": "导出",
"member": "{count, plural, other{# 成员}}", "member": "{count, plural, other{# 成员}}",
"new_password_must_be_different_from_old_password": "新密码必须不同于旧密码",
"edited": "已编辑", "edited": "已编辑",
"bot": "机器人", "bot": "机器人",

View file

@ -501,6 +501,7 @@
"re_generate_key": "重新產生金鑰", "re_generate_key": "重新產生金鑰",
"export": "匯出", "export": "匯出",
"member": "{count, plural, one{# 位成員} other{# 位成員}}", "member": "{count, plural, one{# 位成員} other{# 位成員}}",
"new_password_must_be_different_from_old_password": "新密碼必須與舊密碼不同",
"edited": "已編輯", "edited": "已編輯",
"bot": "機器人", "bot": "機器人",

View file

@ -14,9 +14,10 @@ import { ProfileSettingContentHeader, ProfileSettingContentWrapper } from "@/com
// helpers // helpers
import { authErrorHandler } from "@/helpers/authentication.helper"; import { authErrorHandler } from "@/helpers/authentication.helper";
import { E_PASSWORD_STRENGTH, getPasswordStrength } from "@/helpers/password.helper"; import { E_PASSWORD_STRENGTH, getPasswordStrength } from "@/helpers/password.helper";
// hooks
import { useUser } from "@/hooks/store";
// services // services
import { AuthService } from "@/services/auth.service"; import { AuthService } from "@/services/auth.service";
import { UserService } from "@/services/user.service";
export interface FormValues { export interface FormValues {
old_password: string; old_password: string;
@ -30,7 +31,6 @@ const defaultValues: FormValues = {
confirm_password: "", confirm_password: "",
}; };
const userService = new UserService();
const authService = new AuthService(); const authService = new AuthService();
const defaultShowPassword = { const defaultShowPassword = {
@ -40,10 +40,13 @@ const defaultShowPassword = {
}; };
const SecurityPage = observer(() => { const SecurityPage = observer(() => {
// store
const { data: currentUser, changePassword } = useUser();
// states // states
const [showPassword, setShowPassword] = useState(defaultShowPassword); const [showPassword, setShowPassword] = useState(defaultShowPassword);
const [isPasswordInputFocused, setIsPasswordInputFocused] = useState(false); const [isPasswordInputFocused, setIsPasswordInputFocused] = useState(false);
const [isRetryPasswordInputFocused, setIsRetryPasswordInputFocused] = useState(false); const [isRetryPasswordInputFocused, setIsRetryPasswordInputFocused] = useState(false);
// use form // use form
const { const {
control, control,
@ -56,6 +59,7 @@ const SecurityPage = observer(() => {
const oldPassword = watch("old_password"); const oldPassword = watch("old_password");
const password = watch("new_password"); const password = watch("new_password");
const confirmPassword = watch("confirm_password"); const confirmPassword = watch("confirm_password");
const oldPasswordRequired = !currentUser?.is_password_autoset;
// i18n // i18n
const { t } = useTranslation(); const { t } = useTranslation();
@ -70,8 +74,8 @@ const SecurityPage = observer(() => {
const csrfToken = await authService.requestCSRFToken().then((data) => data?.csrf_token); const csrfToken = await authService.requestCSRFToken().then((data) => data?.csrf_token);
if (!csrfToken) throw new Error("csrf token not found"); if (!csrfToken) throw new Error("csrf token not found");
await userService.changePassword(csrfToken, { await changePassword(csrfToken, {
old_password, ...(oldPasswordRequired && { old_password }),
new_password, new_password,
}); });
@ -95,7 +99,7 @@ const SecurityPage = observer(() => {
const isButtonDisabled = const isButtonDisabled =
getPasswordStrength(password) != E_PASSWORD_STRENGTH.STRENGTH_VALID || getPasswordStrength(password) != E_PASSWORD_STRENGTH.STRENGTH_VALID ||
oldPassword.trim() === "" || (oldPasswordRequired && oldPassword.trim() === "") ||
password.trim() === "" || password.trim() === "" ||
confirmPassword.trim() === "" || confirmPassword.trim() === "" ||
password !== confirmPassword || password !== confirmPassword ||
@ -115,41 +119,43 @@ const SecurityPage = observer(() => {
<ProfileSettingContentHeader title={t("auth.common.password.change_password.label.default")} /> <ProfileSettingContentHeader title={t("auth.common.password.change_password.label.default")} />
<form onSubmit={handleSubmit(handleChangePassword)} className="flex flex-col gap-8 py-6"> <form onSubmit={handleSubmit(handleChangePassword)} className="flex flex-col gap-8 py-6">
<div className="flex flex-col gap-10 w-full max-w-96"> <div className="flex flex-col gap-10 w-full max-w-96">
<div className="space-y-1"> {oldPasswordRequired && (
<h4 className="text-sm">{t("auth.common.password.current_password.label")}</h4> <div className="space-y-1">
<div className="relative flex items-center rounded-md"> <h4 className="text-sm">{t("auth.common.password.current_password.label")}</h4>
<Controller <div className="relative flex items-center rounded-md">
control={control} <Controller
name="old_password" control={control}
rules={{ name="old_password"
required: t("common.errors.required"), rules={{
}} required: t("common.errors.required"),
render={({ field: { value, onChange } }) => ( }}
<Input render={({ field: { value, onChange } }) => (
id="old_password" <Input
type={showPassword?.oldPassword ? "text" : "password"} id="old_password"
value={value} type={showPassword?.oldPassword ? "text" : "password"}
onChange={onChange} value={value}
placeholder={t("old_password")} onChange={onChange}
className="w-full" placeholder={t("old_password")}
hasError={Boolean(errors.old_password)} className="w-full"
hasError={Boolean(errors.old_password)}
/>
)}
/>
{showPassword?.oldPassword ? (
<EyeOff
className="absolute right-3 h-5 w-5 stroke-custom-text-400 hover:cursor-pointer"
onClick={() => handleShowPassword("oldPassword")}
/>
) : (
<Eye
className="absolute right-3 h-5 w-5 stroke-custom-text-400 hover:cursor-pointer"
onClick={() => handleShowPassword("oldPassword")}
/> />
)} )}
/> </div>
{showPassword?.oldPassword ? ( {errors.old_password && <span className="text-xs text-red-500">{errors.old_password.message}</span>}
<EyeOff
className="absolute right-3 h-5 w-5 stroke-custom-text-400 hover:cursor-pointer"
onClick={() => handleShowPassword("oldPassword")}
/>
) : (
<Eye
className="absolute right-3 h-5 w-5 stroke-custom-text-400 hover:cursor-pointer"
onClick={() => handleShowPassword("oldPassword")}
/>
)}
</div> </div>
{errors.old_password && <span className="text-xs text-red-500">{errors.old_password.message}</span>} )}
</div>
<div className="space-y-1"> <div className="space-y-1">
<h4 className="text-sm">{t("auth.common.password.new_password.label")}</h4> <h4 className="text-sm">{t("auth.common.password.new_password.label")}</h4>
<div className="relative flex items-center rounded-md"> <div className="relative flex items-center rounded-md">

View file

@ -145,7 +145,7 @@ export class UserService extends APIService {
}); });
} }
async changePassword(token: string, data: { old_password: string; new_password: string }): Promise<any> { async changePassword(token: string, data: { old_password?: string; new_password: string }): Promise<any> {
return this.post(`/auth/change-password/`, data, { return this.post(`/auth/change-password/`, data, {
headers: { headers: {
"X-CSRFTOKEN": token, "X-CSRFTOKEN": token,

View file

@ -41,6 +41,10 @@ export interface IUserStore {
updateCurrentUser: (data: Partial<IUser>) => Promise<IUser | undefined>; updateCurrentUser: (data: Partial<IUser>) => Promise<IUser | undefined>;
handleSetPassword: (csrfToken: string, data: { password: string }) => Promise<IUser | undefined>; handleSetPassword: (csrfToken: string, data: { password: string }) => Promise<IUser | undefined>;
deactivateAccount: () => Promise<void>; deactivateAccount: () => Promise<void>;
changePassword: (
csrfToken: string,
payload: { old_password?: string; new_password: string }
) => Promise<IUser | undefined>;
reset: () => void; reset: () => void;
signOut: () => Promise<void>; signOut: () => Promise<void>;
// computed // computed
@ -89,6 +93,7 @@ export class UserStore implements IUserStore {
updateCurrentUser: action, updateCurrentUser: action,
handleSetPassword: action, handleSetPassword: action,
deactivateAccount: action, deactivateAccount: action,
changePassword: action,
reset: action, reset: action,
signOut: action, signOut: action,
// computed // computed
@ -200,6 +205,23 @@ export class UserStore implements IUserStore {
} }
}; };
changePassword = async (
csrfToken: string,
payload: {
old_password?: string;
new_password: string;
}
): Promise<IUser | undefined> => {
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 * @description deactivates the current user
* @returns {Promise<void>} * @returns {Promise<void>}