fix(admin): require current password for self password change and fix field ordering

- initialize `newPassword` in form state on load
- add `needsCurrentPassword` flag triggered by email or password change when editing self
- route self password change to profile endpoint with current password verification
- move role tag input above password section and update current password field visibility logic
This commit is contained in:
2026-04-24 15:52:34 -04:00
parent f22fcb6f68
commit ec0edf89b9
@@ -46,6 +46,7 @@ const UserEditModal = ({ userId, currentUserId, isOpen, onClose, onSaved }) => {
name: userJson.user.name || '',
email: userJson.user.email || '',
currentPassword: '',
newPassword: '',
});
} else {
toast.error(userJson.message || 'Utilisateur introuvable');
@@ -90,10 +91,12 @@ const UserEditModal = ({ userId, currentUserId, isOpen, onClose, onSaved }) => {
const emailChanged = userData && formData.email !== userData.email;
const needsCurrentPassword = isSelf && (emailChanged || !!formData.newPassword);
const validate = () => {
const newErrors = {};
if (!formData.name?.trim()) newErrors.name = 'Le nom est requis';
if (emailChanged && isSelf && !formData.currentPassword) newErrors.currentPassword = 'Le mot de passe est requis pour changer le courriel';
if (needsCurrentPassword && !formData.currentPassword) newErrors.currentPassword = 'Le mot de passe actuel est requis';
setErrors(newErrors);
return Object.keys(newErrors).length === 0;
};
@@ -175,11 +178,16 @@ const UserEditModal = ({ userId, currentUserId, isOpen, onClose, onSaved }) => {
}
if (formData.newPassword) {
const pwdRes = await fetch(`/zen/api/users/${userId}/password`, {
method: 'PUT',
const pwdUrl = isSelf ? '/zen/api/users/profile/password' : `/zen/api/users/${userId}/password`;
const pwdMethod = isSelf ? 'POST' : 'PUT';
const pwdBody = isSelf
? { currentPassword: formData.currentPassword, newPassword: formData.newPassword }
: { newPassword: formData.newPassword };
const pwdRes = await fetch(pwdUrl, {
method: pwdMethod,
headers: { 'Content-Type': 'application/json' },
credentials: 'include',
body: JSON.stringify({ newPassword: formData.newPassword }),
body: JSON.stringify(pwdBody),
});
const pwdData = await pwdRes.json();
if (!pwdRes.ok) {
@@ -242,17 +250,17 @@ const UserEditModal = ({ userId, currentUserId, isOpen, onClose, onSaved }) => {
placeholder="courriel@exemple.com"
/>
</div>
{isSelf && emailChanged && (
<Input
label="Mot de passe actuel *"
type="password"
value={formData.currentPassword}
onChange={(value) => handleInputChange('currentPassword', value)}
placeholder="Votre mot de passe"
error={errors.currentPassword}
description="Requis pour confirmer le changement de courriel"
/>
<TagInput
label="Rôles attribués"
options={roleOptions}
value={selectedRoleIds}
onChange={setSelectedRoleIds}
placeholder="Rechercher un rôle..."
renderTag={(opt, onRemove) => (
<RoleBadge key={opt.value} name={opt.label} color={opt.color} onRemove={onRemove} />
)}
/>
<div className="border-t border-neutral-200 dark:border-neutral-700 pt-4 flex flex-col gap-3">
<Input
@@ -275,16 +283,17 @@ const UserEditModal = ({ userId, currentUserId, isOpen, onClose, onSaved }) => {
</div>
</div>
<TagInput
label="Rôles attribués"
options={roleOptions}
value={selectedRoleIds}
onChange={setSelectedRoleIds}
placeholder="Rechercher un rôle..."
renderTag={(opt, onRemove) => (
<RoleBadge key={opt.value} name={opt.label} color={opt.color} onRemove={onRemove} />
)}
{needsCurrentPassword && (
<Input
label="Mot de passe actuel *"
type="password"
value={formData.currentPassword}
onChange={(value) => handleInputChange('currentPassword', value)}
placeholder="Votre mot de passe"
error={errors.currentPassword}
description={emailChanged && formData.newPassword ? 'Requis pour confirmer le changement de courriel et de mot de passe' : emailChanged ? 'Requis pour confirmer le changement de courriel' : 'Requis pour confirmer le changement de mot de passe'}
/>
)}
</div>
)}
</Modal>