feat(auth): real JWT login, RBAC routes, RGPD cookie banner + users/journal admin
Build & Deploy / build (push) Successful in 11s
Build & Deploy / build (push) Successful in 11s
This commit is contained in:
@@ -1,14 +1,23 @@
|
||||
/* Page de connexion : formulaire + SSO Windows */
|
||||
/* Page de connexion : formulaire avec authentification JWT + captcha anti-bot */
|
||||
|
||||
import { useState } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { useAuth } from '../../context/AuthContext';
|
||||
import { getHealth } from '../../services/api';
|
||||
import styles from './Login.module.css';
|
||||
|
||||
/* Génère deux entiers aléatoires pour le captcha additif */
|
||||
function genererCaptcha() {
|
||||
return {
|
||||
x: Math.floor(Math.random() * 10) + 1,
|
||||
y: Math.floor(Math.random() * 10) + 1,
|
||||
};
|
||||
}
|
||||
|
||||
export default function Login() {
|
||||
const [identifiant, setIdentifiant] = useState('');
|
||||
const [motDePasse, setMotDePasse] = useState('');
|
||||
const [captcha, setCaptcha] = useState(() => genererCaptcha());
|
||||
const [reponseCaptcha, setReponseCaptcha] = useState('');
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [erreur, setErreur] = useState('');
|
||||
|
||||
@@ -18,25 +27,29 @@ export default function Login() {
|
||||
async function handleSubmit(e) {
|
||||
e.preventDefault();
|
||||
setErreur('');
|
||||
setLoading(true);
|
||||
|
||||
/* Vérification anti-bot du captcha avant tout appel réseau */
|
||||
if (parseInt(reponseCaptcha, 10) !== captcha.x + captcha.y) {
|
||||
setErreur('Réponse au captcha incorrecte.');
|
||||
setCaptcha(genererCaptcha());
|
||||
setReponseCaptcha('');
|
||||
return;
|
||||
}
|
||||
|
||||
setLoading(true);
|
||||
try {
|
||||
/* Vérification de la disponibilité de l'API avant connexion */
|
||||
await getHealth();
|
||||
login({ nom: identifiant || 'Utilisateur', role: 'user', token: 'session' });
|
||||
await login(identifiant, motDePasse);
|
||||
navigate('/dashboard');
|
||||
} catch {
|
||||
setErreur('Impossible de contacter le serveur. Vérifiez que l\'API est démarrée sur le port 8000.');
|
||||
} catch (err) {
|
||||
/* Message générique en cas d'identifiants invalides ou d'erreur serveur */
|
||||
setErreur(err.message === 'Identifiants invalides' ? 'Identifiants invalides' : (err.message || 'Identifiants invalides'));
|
||||
setCaptcha(genererCaptcha());
|
||||
setReponseCaptcha('');
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}
|
||||
|
||||
function handleSso() {
|
||||
/* Connexion SSO Windows : même vérification API */
|
||||
handleSubmit({ preventDefault: () => {} });
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={styles.page}>
|
||||
<div className={styles.card}>
|
||||
@@ -73,6 +86,21 @@ export default function Login() {
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className={styles.field}>
|
||||
<label className={styles.label} htmlFor="captcha">
|
||||
Vérification : combien font {captcha.x} + {captcha.y} ?
|
||||
</label>
|
||||
<input
|
||||
id="captcha"
|
||||
type="number"
|
||||
className={styles.input}
|
||||
value={reponseCaptcha}
|
||||
onChange={e => setReponseCaptcha(e.target.value)}
|
||||
placeholder="Votre réponse"
|
||||
autoComplete="off"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{erreur && <p className={styles.erreur}>{erreur}</p>}
|
||||
|
||||
<button type="submit" className={styles.btnPrimary} disabled={loading}>
|
||||
@@ -80,14 +108,6 @@ export default function Login() {
|
||||
</button>
|
||||
</form>
|
||||
|
||||
<div className={styles.divider}>
|
||||
<span>ou</span>
|
||||
</div>
|
||||
|
||||
<button type="button" className={styles.btnSecondary} onClick={handleSso} disabled={loading}>
|
||||
Connexion Windows SSO
|
||||
</button>
|
||||
|
||||
<div className={styles.links}>
|
||||
<a href="#" className={styles.forgotLink}>Mot de passe oublié ?</a>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user