Authentification et autorisation
Le backend utilise des jetons JWT access sans état plus des refresh tokens rotatifs. Les permissions sont chargées depuis la base à chaque requête ; elles ne sont pas embarquées dans le jeton.
Jeton d’accès
Section intitulée « Jeton d’accès »Le jeton d’accès ne porte que l’identité et un identifiant de jeton (jti) :
export interface AccessTokenClaims { sub: BigIntStr; // admin.utilisateur.id_utilisateur login: string; role: string; jti: string; // pour la denylist Redis}TokensService.signAccessToken signe le JWT et retourne sa TTL en secondes :
signAccessToken(user: { id: BigIntStr; login: string; role: string }) { const jti = randomBytes(16).toString('hex'); const token = this.jwt.sign( { login: user.login, role: user.role }, { subject: user.id, jwtid: jti }, ); // ...}Refresh token
Section intitulée « Refresh token »Les refresh tokens sont des chaînes opaques base64url de 32 octets. Seul le hachage SHA-256 est persisté :
generateRefreshToken(): RefreshTokenResult { const raw = randomBytes(32).toString('base64url'); return { raw, hash: sha256(raw) };}Sur POST /api/v1/auth/refresh :
- L’ancien hachage de refresh est révoqué.
- Un nouveau refresh token est généré.
replaced_by_hashenregistre la lignée.- Si un jeton révoqué est réutilisé, toute la famille est révoquée.
Endpoints d’authentification
Section intitulée « Endpoints d’authentification »| Méthode | Route | Accès | Throttle | Rôle |
|---|---|---|---|---|
POST |
/api/v1/auth/login |
public | 5/60s par IP | Jetons access + refresh |
POST |
/api/v1/auth/inscription |
public | 3/60s par IP | Auto-inscription en CLIENT |
POST |
/api/v1/auth/mot-de-passe-oublie |
public | 5/60s par IP | Email de réinitialisation |
POST |
/api/v1/auth/reinitialiser-mot-de-passe |
public | 5/60s par IP | Applique le nouveau mot de passe depuis le jeton |
POST |
/api/v1/auth/refresh |
public | aucun | Rotation du refresh token |
POST |
/api/v1/auth/logout |
public | aucun | Révoque refresh + jti access |
GET |
/api/v1/auth/me |
JWT | — | Utilisateur courant + permissions |
Hachage des mots de passe
Section intitulée « Hachage des mots de passe »PasswordService utilise bcryptjs avec un coût de 10 :
@Injectable()export class PasswordService { async verify(motDePasse: string, hash: string): Promise<boolean> { try { return await bcrypt.compare(motDePasse, hash); } catch { return false; } } async hash(motDePasse: string): Promise<string> { return bcrypt.hash(motDePasse, 10); }}La création admin et la réinitialisation hachent directement avec bcrypt.hash(..., 10).
Permissions et RBAC
Section intitulée « Permissions et RBAC »PermissionsService charge les codes de permission d’un rôle avec un cache de 60 secondes :
async forRole(roleCode: string): Promise<ReadonlySet<string>> { // cache hit ? const codes = await this.repo.loadPermissionCodes(this.scope.root, roleCode); return new Set(codes);}Le guard vérifie les métadonnées @RequirePermissions après JwtAuthGuard :
@Get('utilisateurs')@RequirePermissions('ADMIN.VOIR')listerUtilisateurs(...) { ... }AuthUser porte aussi des clés de périmètre résolues depuis la base :
export interface AuthUser { id: BigIntStr; login: string; role: string; permissions: ReadonlySet<string>; jti: string; idClient: BigIntStr | null; idCaisse: BigIntStr | null; idActiviteScope: BigIntStr | null;}Guards globaux
Section intitulée « Guards globaux »auth.module.ts enregistre les deux guards globalement dans l’ordre :
{ provide: APP_GUARD, useClass: JwtAuthGuard },{ provide: APP_GUARD, useClass: PermissionsGuard },JwtAuthGuard ignore les routes @Public(), vérifie le Bearer, contrôle la denylist Redis pour le jti, puis construit AuthUser.
PermissionsGuard autorise tout utilisateur authentifié quand aucun @RequirePermissions n’est déclaré ; sinon il vérifie l’ensemble de permissions de l’utilisateur.
Module admin
Section intitulée « Module admin »src/admin/admin.controller.ts expose la gestion des utilisateurs, rôles et permissions :
| Route | Permission | Rôle |
|---|---|---|
GET /api/v1/admin/utilisateurs |
ADMIN.VOIR |
Lister les utilisateurs |
POST /api/v1/admin/utilisateurs |
ADMIN.CREER |
Créer un utilisateur |
PATCH /api/v1/admin/utilisateurs/:id |
ADMIN.MODIFIER |
Modifier un utilisateur |
POST /api/v1/admin/utilisateurs/:id/reinitialiser-mot-de-passe |
ADMIN.MODIFIER |
Réinitialiser le mot de passe |
GET /api/v1/admin/roles |
ADMIN.VOIR |
Lister les rôles |
POST /api/v1/admin/roles |
ADMIN.CREER |
Créer un rôle |
DELETE /api/v1/admin/roles/:id |
ADMIN.MODIFIER |
Supprimer un rôle (après retrait de ses permissions) |
GET /api/v1/admin/permissions |
ADMIN.VOIR |
Catalogue des permissions |
AdminRepository exclut volontairement mot_de_passe des listes de colonnes en lecture.
Révocation de jeton au logout
Section intitulée « Révocation de jeton au logout »Le logout révoque le refresh token et ajoute le jti du jeton d’accès à la denylist Redis pour la TTL restante :
if (authorizationHeader?.startsWith('Bearer ')) { const token = authorizationHeader.slice('Bearer '.length).trim(); const claims = await this.tokens.verifyAccessToken(token); await this.tokenStore.revoke(claims.jti, this.tokens.accessTtlSeconds());}