119 lines
3.7 KiB
JavaScript
119 lines
3.7 KiB
JavaScript
// 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 <token>).
|
||
* 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.' });
|
||
}
|