// Authentification : vérification des identifiants et émission/validation des JWT. import jwt from 'jsonwebtoken'; import { JWT_SECRET, JWT_EXPIRATION_MINUTES, ADMIN_USER, ADMIN_PASSWORD, estAdmin } from './config.js'; import { verifierConnexion, trouverUtilisateur } from './users.js'; /** * Vérifie les identifiants. * Ordre : d'abord l'ADMIN du .env, puis les comptes gérés (store users.json). * * Retourne un objet décrivant le résultat : * - { ok:true, login, role, admin, doitChangerMdp } * - { ok:false, raison } avec raison ∈ 'identifiants' | 'desactive' */ export async function verifierIdentifiants(login, motDePasse) { if (typeof login !== 'string' || typeof motDePasse !== 'string') { return { ok: false, raison: 'identifiants' }; } // 1) Compte administrateur (défini dans le .env, hors store). if (ADMIN_USER && login === ADMIN_USER) { if (motDePasse === ADMIN_PASSWORD) { return { ok: true, login, role: 'admin', admin: true, doitChangerMdp: false }; } return { ok: false, raison: 'identifiants' }; } // 2) Comptes gérés via le site. const res = await verifierConnexion(login, motDePasse); if (res.ok) { return { ok: true, login: res.utilisateur.login, role: res.utilisateur.role, admin: false, doitChangerMdp: res.utilisateur.doitChangerMdp }; } if (res.raison === 'desactive') { return { ok: false, raison: 'desactive' }; } return { ok: false, raison: 'identifiants' }; } /** * Émet un JWT signé. Le token ne porte que le login (`sub`) : le rôle est relu à * chaque requête depuis le store, pour que tout changement de rôle ou désactivation * décidé par l'admin prenne effet immédiatement, sans attendre une reconnexion. */ export function emettreJeton(login) { return jwt.sign({ sub: login }, JWT_SECRET, { expiresIn: `${JWT_EXPIRATION_MINUTES}m` }); } /** * Middleware Express : exige un JWT valide (Authorization: Bearer ). * Renseigne req.utilisateur = { login, role, admin }. Le rôle est relu en direct : * - admin du .env -> role 'admin' ; * - sinon rôle courant du store ; un compte supprimé ou désactivé est refusé (401/403). */ export async function exigerAuth(req, res, next) { const entete = req.headers.authorization || ''; const [schema, jeton] = entete.split(' '); if (schema !== 'Bearer' || !jeton) { return res.status(401).json({ erreur: 'Authentification requise.' }); } let charge; try { charge = jwt.verify(jeton, JWT_SECRET); } catch { return res.status(401).json({ erreur: 'Session invalide ou expirée.' }); } const login = charge.sub; if (estAdmin(login)) { req.utilisateur = { login, role: 'admin', admin: true }; return next(); } // Rôle relu depuis le store à chaque requête. const u = await trouverUtilisateur(login); if (!u) { return res.status(401).json({ erreur: 'Compte introuvable.' }); } if (u.role === 'desactive') { return res.status(403).json({ erreur: 'Ce compte est désactivé.' }); } req.utilisateur = { login, role: u.role, admin: false }; return next(); } /** Middleware : réservé à l'administrateur. */ export function exigerAdmin(req, res, next) { if (!req.utilisateur?.admin) { return res.status(403).json({ erreur: 'Réservé à l’administrateur.' }); } return next(); } /** * Middleware : exige un rôle autorisé à contribuer (annoter). * Admin et contributeur passent ; consultation est refusé (403). */ export function exigerContributeur(req, res, next) { const role = req.utilisateur?.role; if (role === 'admin' || role === 'contributeur') { return next(); } return res.status(403).json({ erreur: 'Votre accès est en lecture seule.' }); }