Sécurité
Authentification — double token
L'API utilise un système access token + refresh token :
| Token | Durée | Usage | Stockage côté client |
|---|---|---|---|
| Access token | 15 min | En-tête Authorization: Bearer <token> sur chaque requête | Mémoire (jamais en localStorage) |
| Refresh token | 7 jours | Échangé contre un nouveau couple via POST /auth/refresh | localStorage (révocable côté serveur) |
Rotation
À chaque appel POST /auth/refresh :
- Le refresh token courant est révoqué côté DB (
revokedAt = now()) - Un nouveau couple access + refresh est émis
- Le nouveau refresh remplace l'ancien dans le localStorage du client
Conséquence : un refresh volé devient inutilisable dès la rotation suivante. Si le serveur reçoit un refresh déjà révoqué, il rejette l'appel et marque la session comme suspecte.
Révocation explicite
Le POST /auth/logout révoque le refresh token fourni. Le POST /auth/change-password révoque toutes les sessions du user.
Côté admin, POST /admin/users/:id/reset-password révoque toutes les sessions de l'utilisateur cible.
Mots de passe
- Algorithme : Argon2id via le package
argon2(jamais bcrypt, jamais SHA brut) - Paramètres : valeurs par défaut du package (memory cost ≥ 64 MB, time cost ≥ 3, parallelism ≥ 4)
- Politique : 8–128 caractères, au moins une lettre et un chiffre (validée par Zod,
passwordSchema). Volontairement modérée (public non technique) — pas d'exigence majuscule/symbole, conformément à l'esprit OWASP qui décourage les règles de composition lourdes. - Anti-énumération : le login exécute toujours une vérification Argon2 (contre un hash factice si l'identifiant est inconnu) → temps de réponse constant, pas de fuite de l'existence d'un compte par timing.
RBAC
L'autorisation s'appuie sur deux mécanismes complémentaires :
1. Rôle JWT (@Roles)
Le JWT porte la claim role ('ADMIN' | 'TEACHER' | 'LEARNER'). Le decorator @Roles('ADMIN') sur un controller ou une méthode bloque les autres rôles via RolesGuard (403).
2. Isolation par propriété
Le rôle ne suffit pas : une formatrice ne doit pas voir les cours d'une autre formatrice. Le contrôle se fait au niveau service :
async findAll(user: JwtClaims) {
return this.prisma.course.findMany({
where: { teacherId: user.sub }, // isolation par owner
});
}
Ne jamais s'appuyer uniquement sur le rôle pour autoriser un accès. La règle s'écrit : "TEACHER peut accéder, ET le cours doit lui appartenir".
Validation des payloads
ZodValidationPipeappliqué globalement- Les schémas viennent de
packages/contracts/src/ - Toute requête avec un payload non conforme est rejetée en
400 Bad Requestavec le détail des champs invalides
Conséquence : les services Nest peuvent faire confiance aux types reçus, ils sont garantis valides.
Secrets
- Zéro secret en git. Tout dans
.env(ignoré),.env.examplecommité avec valeurs factices - Token GitLab API stocké dans
.envracine (GITLAB_TOKEN), jamais dansapps/api/.env - JWT_SECRET : ≥ 32 caractères aléatoires, généré via
openssl rand -base64 48, différent en dev et prod
Rotation des secrets
| Secret | Fréquence recommandée | Conséquence à la rotation |
|---|---|---|
JWT_SECRET | Tous les 6 mois | Toutes les sessions invalidées au déploiement |
| Token GitLab API | Tous les 3 mois ou dès suspicion d'exposition | Re-stocker dans .env |
| Mot de passe DB | À chaque membre quittant l'équipe | Re-stocker, redémarrer l'API |
Procédure — rotation du token GitLab (GITLAB_TOKEN)
- Révoquer l'ancien PAT : GitLab → Settings → Access Tokens → révoquer le token courant.
- Créer un nouveau PAT (scopes minimaux :
api,read_repository) ; noter la valeur. - Mettre à jour
.envracine (GITLAB_TOKEN=…) sur chaque poste qui s'en sert, et la variable CI si applicable. - Vérifier :
curl --header "PRIVATE-TOKEN: $GITLAB_TOKEN" https://gitlab.id2real.net/api/v4/user.
À faire immédiatement si le token a pu fuiter (capture, log, copier-coller dans un outil tiers). Le
.envest gitignoré, mais un token vu hors du.envdoit être considéré comme compromis.
Headers HTTP
Le backend active au boot :
- Helmet — pose les headers
Content-Security-Policy,X-Frame-Options,X-Content-Type-Options,Strict-Transport-Security, etc. @nestjs/throttler— limitation de taux (par IP, par défaut)- CORS — whitelist stricte des origines (
http://localhost:5173en dev, domaine prod en prod)
Téléchargement de fichiers (avatars)
L'endpoint POST /auth/me/avatar accepte un upload multipart/form-data avec :
- Taille max : 2 Mo
- Types acceptés :
image/png,image/jpeg,image/webp - Validation côté serveur : magic bytes (pas de confiance dans l'extension ni le
Content-Typeenvoyé par le client)
Les fichiers sont stockés sous apps/api/uploads/ en MVP. Migration prévue vers S3/R2/Cloudinary en v1.1.
Soft delete et RGPD
Les "suppressions" sont logiques (deletedAt), pas physiques. Pour répondre à une demande RGPD d'effacement complet, prévoir un script dédié qui :
- Anonymise les données conservables (factures, traces d'inscription liées aux revenus de la formatrice)
- Purge les PII (email, nom, avatar, bio) — les remplace par
[deleted-user-{id}] - Conserve uniquement l'identifiant pour préserver les liens FK
Cette procédure n'est pas automatisée en MVP : à exécuter manuellement sur demande, avec trace de la requête.
Audit log
Pas d'audit log structuré en MVP. Les opérations sensibles à tracer (création de user admin, changement de rôle, reset de password) sont visibles via :
- Les logs applicatifs Nest (niveau INFO et au-delà)
- L'historique Prisma des
created_at/updated_atsur les entités
Mise en place d'un AuditEntry dédié envisagée en v1.1.