From 01526a55514218ec9d684120d4e4851885acbd0d Mon Sep 17 00:00:00 2001 From: Olivier Date: Mon, 15 Jun 2026 22:44:17 +0200 Subject: [PATCH] =?UTF-8?q?R=C3=A8gle=20de=20gestion=20des=20mots=20de=20p?= =?UTF-8?q?asse?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/components/PasswordStrength.jsx | 94 ++++++++++++++++++++ frontend/src/pages/InvitationRegister.jsx | 2 + frontend/src/pages/MonCompte.jsx | 2 + frontend/src/pages/Register.jsx | 4 +- frontend/src/pages/ResetPassword.jsx | 2 + frontend/src/pages/admin/UsersSection.jsx | 2 + 6 files changed, 105 insertions(+), 1 deletion(-) create mode 100644 frontend/src/components/PasswordStrength.jsx diff --git a/frontend/src/components/PasswordStrength.jsx b/frontend/src/components/PasswordStrength.jsx new file mode 100644 index 0000000..40156ba --- /dev/null +++ b/frontend/src/components/PasswordStrength.jsx @@ -0,0 +1,94 @@ +/** + * PasswordStrength — indicateur de complexité de mot de passe. + * + * Props : + * password : string — valeur en cours de saisie + * + * Affiche uniquement quand password est non vide. + */ + +const RULES = [ + { id: 'len', label: '8 caractères minimum', test: p => p.length >= 8 }, + { id: 'upper', label: 'Une lettre majuscule (A–Z)', test: p => /[A-Z]/.test(p) }, + { id: 'lower', label: 'Une lettre minuscule (a–z)', test: p => /[a-z]/.test(p) }, + { id: 'digit', label: 'Un chiffre (0–9)', test: p => /[0-9]/.test(p) }, + { id: 'special', label: 'Un caractère spécial (!@#…)', test: p => /[^A-Za-z0-9]/.test(p) }, +]; + +const LEVELS = [ + { min: 0, label: 'Très faible', color: '#ef4444' }, + { min: 1, label: 'Faible', color: '#f97316' }, + { min: 2, label: 'Moyen', color: '#eab308' }, + { min: 3, label: 'Bon', color: '#84cc16' }, + { min: 4, label: 'Fort', color: '#22c55e' }, + { min: 5, label: 'Très fort', color: '#10b981' }, +]; + +function getLevel(score) { + // score = nombre de règles respectées (0..5) + for (let i = LEVELS.length - 1; i >= 0; i--) { + if (score >= LEVELS[i].min) return LEVELS[i]; + } + return LEVELS[0]; +} + +export default function PasswordStrength({ password }) { + if (!password) return null; + + const results = RULES.map(r => ({ ...r, ok: r.test(password) })); + const score = results.filter(r => r.ok).length; + const level = getLevel(score); + const pct = Math.round((score / RULES.length) * 100); + + return ( +
+ {/* Barre de progression */} +
+
+
+
+ + {level.label} + +
+ + {/* Liste des règles */} +
    + {results.map(r => ( +
  • + {r.ok ? ( + + + + ) : ( + + + + )} + {r.label} +
  • + ))} +
+
+ ); +} diff --git a/frontend/src/pages/InvitationRegister.jsx b/frontend/src/pages/InvitationRegister.jsx index ae14ada..9877b9f 100644 --- a/frontend/src/pages/InvitationRegister.jsx +++ b/frontend/src/pages/InvitationRegister.jsx @@ -1,4 +1,5 @@ import { useState, useEffect } from 'react'; +import PasswordStrength from '../components/PasswordStrength.jsx'; import { useParams, useNavigate, Link } from 'react-router-dom'; // Wrap défini en dehors du composant pour éviter le remontage à chaque frappe @@ -173,6 +174,7 @@ export default function InvitationRegister() {
set('password', e.target.value)} style={{ width: '100%' }} /> +
diff --git a/frontend/src/pages/MonCompte.jsx b/frontend/src/pages/MonCompte.jsx index f9fd27e..82e4d65 100644 --- a/frontend/src/pages/MonCompte.jsx +++ b/frontend/src/pages/MonCompte.jsx @@ -1,4 +1,5 @@ import { useEffect, useRef, useState } from 'react'; +import PasswordStrength from '../components/PasswordStrength.jsx'; import { useLocation, useNavigate } from 'react-router-dom'; import { useAuth } from '../context/AuthContext.jsx'; import { useUi } from '../context/UiContext.jsx'; @@ -470,6 +471,7 @@ function SecurityForm() { setPwdForm({ ...pwdForm, newPassword: e.target.value })} /> +
diff --git a/frontend/src/pages/Register.jsx b/frontend/src/pages/Register.jsx index 3974260..d88c832 100644 --- a/frontend/src/pages/Register.jsx +++ b/frontend/src/pages/Register.jsx @@ -1,6 +1,7 @@ import { useState, useEffect } from 'react'; import { Link, useNavigate } from 'react-router-dom'; import { useAuth } from '../context/AuthContext.jsx'; +import PasswordStrength from '../components/PasswordStrength.jsx'; export default function Register() { const { register } = useAuth(); @@ -205,7 +206,7 @@ export default function Register() {
+
diff --git a/frontend/src/pages/admin/UsersSection.jsx b/frontend/src/pages/admin/UsersSection.jsx index 27b0621..9284a36 100644 --- a/frontend/src/pages/admin/UsersSection.jsx +++ b/frontend/src/pages/admin/UsersSection.jsx @@ -4,6 +4,7 @@ import { api } from '../../api.js'; import ConfirmModal from '../../components/ConfirmModal.jsx'; import Modal from '../../components/Modal.jsx'; import ResultBanner from '../../components/ResultBanner.jsx'; +import PasswordStrength from '../../components/PasswordStrength.jsx'; import { fmt, Badge, UserStatusBadge } from './adminHelpers.jsx'; // ── Helpers ──────────────────────────────────────────────────────────────── @@ -356,6 +357,7 @@ function CreateUserModal({ open, onClose, onCreated }) {
set('password', e.target.value)} placeholder="8 caractères minimum" /> +