PD-238 — Livrable Agent Developer : MFA Management Service & Controller¶
Tâche 4 du manifest de décomposition Date : 2026-02-06 Agent : agent-developer (Claude)
Périmètre¶
src/modules/auth/mfa/services/mfa-management.service.tssrc/modules/auth/mfa/controllers/mfa-management.controller.tssrc/modules/auth/guards/mfa-rate-limit.guard.tssrc/modules/auth/mfa/mfa.module.tssrc/modules/auth/auth.module.ts
Fichiers créés/modifiés¶
1. mfa-rate-limit.guard.ts (nouveau)¶
Guard CanActivate avec rate limiting Redis per-userId : - Clé: rate_limit:mfa:{userId}, max 10 req/min, TTL 60s - Identifiant: userId extrait de request.user.sub (JWT) - Throw MfaRateLimitError (ERR-238-RATE-LIMIT, HTTP 429) si dépassé - Pattern Redis identique à RateLimitService (INCR + EXPIRE atomiques) - setRedisClient() pour injection en tests
2. mfa-management.service.ts (nouveau)¶
5 méthodes publiques : - getStatus(userId) : récupère état MFA via KeycloakAdminService - initTotp(userId) : stocke sessionId en Redis, ne le retourne PAS au client - verifyTotp(userId, code) : récupère sessionId du cache, le supprime après activation réussie - disable(userId) : récupère credentialId via getMfaStatus, puis disableMfa - regenerateRecoveryCodes(userId) : récupère credentialId, appelle Keycloak
Fonctionnalités : - Stockage sessionId TOTP en Redis (mfa:totp:session:{userId}, TTL 5min) - Émission d'événements sécurité via SecurityEventEmitter - INV-238-09 : aucun secret loggé (messages génériques)
3. mfa-management.controller.ts (nouveau)¶
5 endpoints sous @Controller('user/mfa') :
| Endpoint | Guards |
|---|---|
| GET /user/mfa/status | OidcJwtAuthGuard, MfaRateLimitGuard |
| POST /user/mfa/totp/init | OidcJwtAuthGuard, MfaRateLimitGuard |
| POST /user/mfa/totp/verify | OidcJwtAuthGuard, MfaRateLimitGuard |
| POST /user/mfa/disable | OidcJwtAuthGuard, ReauthTokenGuard, MfaRateLimitGuard |
| POST /user/mfa/recovery/regenerate | OidcJwtAuthGuard, ReauthTokenGuard, MfaRateLimitGuard |
Décorateurs classe : - @UseFilters(MfaExceptionFilter) - @UseGuards(OidcJwtAuthGuard) (INV-238-01) - @ApiTags('mfa')
4. mfa.module.ts (modifié)¶
- Imports ajoutés :
JwtModule.registerAsync(...),ConfigModule - Providers ajoutés :
KeycloakAdminService,MfaManagementService,ReauthTokenGuard,MfaRateLimitGuard,MfaExceptionFilter - Controllers ajoutés :
MfaManagementController - Exports ajoutés :
MfaManagementService,ReauthTokenGuard
5. auth.module.ts (modifié)¶
- Imports ajoutés :
MfaModule(pourSecurityEventEmitter) - Providers ajoutés :
ReauthService - Controllers ajoutés :
ReauthController
Conformité invariants¶
| Invariant | Statut |
|---|---|
| INV-238-01 | Conforme — @UseGuards(OidcJwtAuthGuard) au niveau classe |
| INV-238-02 | Conforme — userId via @CurrentUser() → user.sub |
| INV-238-03 | Conforme — getStatus retourne |
| INV-238-04 | Conforme — initTotp retourne |
| INV-238-05 | Conforme — Activation uniquement si code valide |
| INV-238-06 | Conforme — disable protégé par ReauthTokenGuard |
| INV-238-07 | Conforme — regenerateRecoveryCodes protégé par ReauthTokenGuard |
| INV-238-08 | Conforme — Recovery codes retournés uniquement après activation/régénération |
| INV-238-09 | Conforme — Aucun secret loggé |
| INV-238-11 | Conforme — MfaRateLimitGuard appliqué (10 req/min/userId) |
Hypothèses documentées¶
-
Redis partagé : Le guard MFA rate limit et le service MFA session utilisent chacun leur propre connexion Redis avec la même configuration.
-
JwtModule dupliqué : MfaModule importe
JwtModule.registerAsync(...)pour que ReauthTokenGuard dispose d'un JwtService. -
MfaModule importé dans AuthModule : Nécessaire pour que ReauthService ait accès à SecurityEventEmitter. Pas de dépendance circulaire.
Fichiers hors périmètre identifiés¶
Aucun.