Initial commit — specs pré-projet SatPRO + site de présentation (viewer, annotations, gestion des accès)

This commit is contained in:
2026-10-12 12:11:04 +02:00
commit 30608afd9e
77 changed files with 20581 additions and 0 deletions
+118
View File
@@ -0,0 +1,118 @@
// 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.' });
}