Aller au contenu principal

Sécurité

Authentification — double token

L'API utilise un système access token + refresh token :

TokenDuréeUsageStockage côté client
Access token15 minEn-tête Authorization: Bearer <token> sur chaque requêteMémoire (jamais en localStorage)
Refresh token7 joursÉchangé contre un nouveau couple via POST /auth/refreshlocalStorage (révocable côté serveur)

Rotation

À chaque appel POST /auth/refresh :

  1. Le refresh token courant est révoqué côté DB (revokedAt = now())
  2. Un nouveau couple access + refresh est émis
  3. 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

  • ZodValidationPipe appliqué globalement
  • Les schémas viennent de packages/contracts/src/
  • Toute requête avec un payload non conforme est rejetée en 400 Bad Request avec 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.example commité avec valeurs factices
  • Token GitLab API stocké dans .env racine (GITLAB_TOKEN), jamais dans apps/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

SecretFréquence recommandéeConséquence à la rotation
JWT_SECRETTous les 6 moisToutes les sessions invalidées au déploiement
Token GitLab APITous les 3 mois ou dès suspicion d'expositionRe-stocker dans .env
Mot de passe DBÀ chaque membre quittant l'équipeRe-stocker, redémarrer l'API

Procédure — rotation du token GitLab (GITLAB_TOKEN)

  1. Révoquer l'ancien PAT : GitLab → Settings → Access Tokens → révoquer le token courant.
  2. Créer un nouveau PAT (scopes minimaux : api, read_repository) ; noter la valeur.
  3. Mettre à jour .env racine (GITLAB_TOKEN=…) sur chaque poste qui s'en sert, et la variable CI si applicable.
  4. 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 .env est gitignoré, mais un token vu hors du .env doit ê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:5173 en 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-Type envoyé 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 :

  1. Anonymise les données conservables (factures, traces d'inscription liées aux revenus de la formatrice)
  2. Purge les PII (email, nom, avatar, bio) — les remplace par [deleted-user-{id}]
  3. 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_at sur les entités

Mise en place d'un AuditEntry dédié envisagée en v1.1.

Voir aussi