Files
SatAff/site/backend/src/auth.js
T

119 lines
3.7 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// 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.' });
}