Analyse 2FA — ProjetESM : état actuel et cible¶
Date : 2026-06-20
Projet : /home/kanmaber/projets/ProjetESM
Référence : 20260620_1200_analyse-2fa-gestionFichePaie.md · 20260620_1201_analyse-2fa-PsychoEnLigne-vs-gestionFichePaie.md
État actuel de ProjetESM¶
ProjetESM est une synthèse intermédiaire : il a hérité des fonctionnalités avancées de PsychoEnLigne (blocage IP, détection suspectes, journal sécurité, révocation sessions), mais porte encore les bugs de gestionFichePaie.
| Fonctionnalité | État |
|---|---|
| Secret TOTP chiffré | ❌ deuxFacteursSecret String? — plain text |
| Backup code → connexion réelle | ❌ retourne {codeValide} sans JWT |
| Renvoi code EMAIL (API réelle) | ✅ twoFactorApi.sendCode() appelé |
| Debug console.log supprimé | ❌ Activer2FAUseCase.ts:129,185 |
| Backup codes sans orphelins | ❌ 2 lots créés (Generer + Activer) |
| Blocage IP / détection suspectes | ✅ déjà présent |
| Journal de sécurité dans l'UI | ✅ déjà présent |
| Gestion sessions dans l'UI | ✅ déjà présent |
| Tests unitaires auth | ❌ zéro |
Backend — cible (meilleur des deux projets)¶
Fonctionnalités à conserver (déjà présentes — PsychoEnLigne)¶
BloquerIPUseCase+DebloquerIPUseCaseDetecterConnexionsSuspectesUseCaseObtenirLogsSecuriteUseCaseRevoquerSessionUseCase,RevoquerToutesSessionsUseCase,RevoquerTousTokensUseCaseListerSessionsActivesUseCaseVerifierEmailUseCase(email obligatoire à l'inscription)
Corrections à apporter¶
1. Chiffrer le secret TOTP au repos (PsychoEnLigne)
Schema Prisma :
- deuxFacteursSecret String? @db.VarChar(255)
+ secret2FAChiffre String? // AES-256 — jamais en plain text
Service2FA : ajouter chiffrerSecret(secret: string) et dechiffrerSecret(chiffre: string)
via crypto.createCipheriv('aes-256-gcm', ...) avec clé dans .env (TOTP_ENCRYPTION_KEY).
2. Supprimer les console.log de debug (PsychoEnLigne)
Activer2FAUseCase.ts:129 et :185 — remplacer par this.logger.debug(...) ou supprimer.
3. Corriger VerifierBackupCodeUseCase pour qu'il complète la connexion
Aucun des deux projets ne l'a fait correctement. Ce use case doit retourner des JWT
exactement comme Verifier2FAUseCase :
// Après bcrypt.compare(code, codeHache) → OK
const { accessToken, refreshToken } = await this.serviceJWT.genererPaireTokens(tokenPayload);
const session = await this.sessionRepository.creerSession({ ... });
return {
succes: true,
codeValide: true,
accessToken: accessToken.token,
refreshToken: refreshToken.token,
expiresIn: ...,
utilisateur: { ... },
};
4. Corriger les backup codes orphelins
Generer2FASecretUseCase — retourner les codes en clair sans les stocker :
const backupCodes = this.service2FA.genererBackupCodes(10);
// NE PAS appeler backupCodeRepository.creer() ici
return { secret2FA: { ..., backupCodes } };
Activer2FAUseCase — seul point de persistance des codes hashés, après confirmation.
Ajout à planifier¶
- Tests unitaires sur
Verifier2FAUseCase,Activer2FAUseCase,VerifierBackupCodeUseCase(modèle PsychoEnLigne — un fichier.test.tspar use case) - Supprimer
Type2FA.SMSdu shared-kernel ou implémenter unServiceSMSréel
Frontend — cible (meilleur des deux projets)¶
Points forts de PsychoEnLigne à conserver / aligner¶
- Architecture DDD complète (domain/entities, value-objects, ports) — déjà en place
TwoFactorSetupPagesans bouton "Configurer plus tard" — déjà OK (pas de page setup problématique)SessionsManager+SecuritySettingsdans l'UI — déjà présents- Code propre sans logs parasites
Point fort de gestionFichePaie à conserver¶
handleResendCodeappelle réellementtwoFactorApi.sendCode()— déjà présent dans ProjetESM ✅
Correction à apporter¶
authStore.verifyBackupCode doit connecter l'utilisateur :
// Actuellement — bug : retourne boolean, ne connecte pas
verifyBackupCode: async (data) => {
const isValid = await container.verifyBackupCodeUseCase.execute(data);
return isValid; // ← l'utilisateur reste déconnecté
}
// Cible — après fix du backend qui retourne des JWT
verifyBackupCode: async (data) => {
set({ isLoading: true, error: null });
try {
const result = await container.verifyBackupCodeUseCase.execute(data);
tokenManager.setTokens(result.accessToken, result.refreshToken);
set({
isLoading: false,
isAuthenticated: true,
requires2FA: false,
twoFactorSessionData: null,
user: result.utilisateur,
error: null,
});
startBackgroundRefresh(result.refreshToken);
} catch (error) {
set({ isLoading: false, error: error.message });
throw error;
}
}
Ce qui manque dans les deux projets (à ajouter)¶
- "Se souvenir de cet appareil" (optionnel) : token de confiance 30 jours en cookie httpOnly,
coté backend
AppareillDeConfianceRepository+ vérification dansConnexionUseCaseavant de déclencher le flow 2FA.
Tableau récapitulatif — les trois projets vs cible¶
| Fonctionnalité | gestionFichePaie | PsychoEnLigne | ProjetESM actuel | ProjetESM cible |
|---|---|---|---|---|
| Secret TOTP chiffré | ❌ | ✅ AES-256 | ❌ | ✅ |
| Backup code → connexion réelle | ❌ | ❌ | ❌ | ✅ à implémenter |
| Renvoi code EMAIL (API) | ✅ | ❌ | ✅ | ✅ déjà OK |
| Debug console.log supprimé | ❌ | ✅ | ❌ | ✅ à nettoyer |
| Backup codes sans orphelins | ❌ | ❌ | ❌ | ✅ à corriger |
| Blocage IP / détection suspectes | ❌ | ✅ | ✅ | ✅ déjà OK |
| Journal de sécurité dans l'UI | ❌ | ✅ | ✅ | ✅ déjà OK |
| Gestion sessions dans l'UI | ❌ | ✅ | ✅ | ✅ déjà OK |
| Pas de bouton "skip" 2FA setup | ❌ | ✅ | ✅ | ✅ déjà OK |
| Tests unitaires auth | partiels | ✅ complets | ❌ zéro | ✅ à écrire |
| "Se souvenir de cet appareil" | ❌ | ❌ | ❌ | planifiable |
Priorités d'implémentation¶
| # | Action | Effort | Impact |
|---|---|---|---|
| 1 | Fix VerifierBackupCodeUseCase → retourner JWT |
Moyen | 🔴 Bloquant |
| 2 | Fix authStore.verifyBackupCode → isAuthenticated: true |
Faible | 🔴 Bloquant |
| 3 | Chiffrement AES-256 secret TOTP (schema + service) | Moyen | 🟠 Sécurité |
| 4 | Supprimer console.log debug |
Faible | 🟠 Sécurité |
| 5 | Fix backup codes orphelins (Generer ne stocke pas) | Faible | 🟠 Intégrité données |
| 6 | Tests unitaires use cases 2FA | Élevé | 🟡 Qualité |
| 7 | Supprimer Type2FA.SMS ou implémenter |
Faible | 🟡 Cohérence |