[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:
parent
99dba80d19
commit
e8779511ad
20 changed files with 101 additions and 41 deletions
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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": {
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
||||
|
|
|
|||
|
|
@ -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": {
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
||||
|
|
|
|||
|
|
@ -501,6 +501,7 @@
|
|||
"re_generate_key": "キーを再生成",
|
||||
"export": "エクスポート",
|
||||
"member": "{count, plural, other{# メンバー}}",
|
||||
"new_password_must_be_different_from_old_password": "新しいパスワードは古いパスワードと異なる必要があります",
|
||||
"edited": "編集済み",
|
||||
"bot": "ボット",
|
||||
|
||||
|
|
|
|||
|
|
@ -501,6 +501,7 @@
|
|||
"re_generate_key": "키 다시 생성",
|
||||
"export": "내보내기",
|
||||
"member": "{count, plural, one{# 멤버} other{# 멤버}}",
|
||||
"new_password_must_be_different_from_old_password": "새 비밀번호는 이전 비밀번호와 다르게 설정해야 합니다",
|
||||
"edited": "수정됨",
|
||||
"bot": "봇",
|
||||
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
||||
|
|
|
|||
|
|
@ -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": {
|
||||
|
|
|
|||
|
|
@ -499,6 +499,7 @@
|
|||
"re_generate_key": "Перегенерировать ключ",
|
||||
"export": "Экспорт",
|
||||
"member": "{count, plural, one{# участник} few{# участника} other{# участников}}",
|
||||
"new_password_must_be_different_from_old_password": "Новое пароль должен отличаться от старого пароля",
|
||||
"edited": "Редактировано",
|
||||
"bot": "Бот",
|
||||
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
||||
|
|
|
|||
|
|
@ -499,6 +499,7 @@
|
|||
"re_generate_key": "Повторно згенерувати ключ",
|
||||
"export": "Експортувати",
|
||||
"member": "{count, plural, one{# учасник} few{# учасники} other{# учасників}}",
|
||||
"new_password_must_be_different_from_old_password": "Новий пароль повинен бути відмінним від старого пароля",
|
||||
"edited": "Редагувано",
|
||||
"bot": "Бот",
|
||||
|
||||
|
|
|
|||
|
|
@ -501,6 +501,7 @@
|
|||
"re_generate_key": "重新生成密钥",
|
||||
"export": "导出",
|
||||
"member": "{count, plural, other{# 成员}}",
|
||||
"new_password_must_be_different_from_old_password": "新密码必须不同于旧密码",
|
||||
"edited": "已编辑",
|
||||
"bot": "机器人",
|
||||
|
||||
|
|
|
|||
|
|
@ -501,6 +501,7 @@
|
|||
"re_generate_key": "重新產生金鑰",
|
||||
"export": "匯出",
|
||||
"member": "{count, plural, one{# 位成員} other{# 位成員}}",
|
||||
"new_password_must_be_different_from_old_password": "新密碼必須與舊密碼不同",
|
||||
"edited": "已編輯",
|
||||
"bot": "機器人",
|
||||
|
||||
|
|
|
|||
|
|
@ -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(() => {
|
|||
<ProfileSettingContentHeader title={t("auth.common.password.change_password.label.default")} />
|
||||
<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="space-y-1">
|
||||
<h4 className="text-sm">{t("auth.common.password.current_password.label")}</h4>
|
||||
<div className="relative flex items-center rounded-md">
|
||||
<Controller
|
||||
control={control}
|
||||
name="old_password"
|
||||
rules={{
|
||||
required: t("common.errors.required"),
|
||||
}}
|
||||
render={({ field: { value, onChange } }) => (
|
||||
<Input
|
||||
id="old_password"
|
||||
type={showPassword?.oldPassword ? "text" : "password"}
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
placeholder={t("old_password")}
|
||||
className="w-full"
|
||||
hasError={Boolean(errors.old_password)}
|
||||
{oldPasswordRequired && (
|
||||
<div className="space-y-1">
|
||||
<h4 className="text-sm">{t("auth.common.password.current_password.label")}</h4>
|
||||
<div className="relative flex items-center rounded-md">
|
||||
<Controller
|
||||
control={control}
|
||||
name="old_password"
|
||||
rules={{
|
||||
required: t("common.errors.required"),
|
||||
}}
|
||||
render={({ field: { value, onChange } }) => (
|
||||
<Input
|
||||
id="old_password"
|
||||
type={showPassword?.oldPassword ? "text" : "password"}
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
placeholder={t("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")}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
{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>
|
||||
{errors.old_password && <span className="text-xs text-red-500">{errors.old_password.message}</span>}
|
||||
</div>
|
||||
{errors.old_password && <span className="text-xs text-red-500">{errors.old_password.message}</span>}
|
||||
</div>
|
||||
)}
|
||||
<div className="space-y-1">
|
||||
<h4 className="text-sm">{t("auth.common.password.new_password.label")}</h4>
|
||||
<div className="relative flex items-center rounded-md">
|
||||
|
|
|
|||
|
|
@ -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, {
|
||||
headers: {
|
||||
"X-CSRFTOKEN": token,
|
||||
|
|
|
|||
|
|
@ -41,6 +41,10 @@ export interface IUserStore {
|
|||
updateCurrentUser: (data: Partial<IUser>) => Promise<IUser | undefined>;
|
||||
handleSetPassword: (csrfToken: string, data: { password: string }) => Promise<IUser | undefined>;
|
||||
deactivateAccount: () => Promise<void>;
|
||||
changePassword: (
|
||||
csrfToken: string,
|
||||
payload: { old_password?: string; new_password: string }
|
||||
) => Promise<IUser | undefined>;
|
||||
reset: () => void;
|
||||
signOut: () => Promise<void>;
|
||||
// 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<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
|
||||
* @returns {Promise<void>}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue