Aller au contenu

PD-27 — Plan d'implémentation

1. Découpage en composants

Composant Responsabilité Fichiers impactés
MfaClaimsValidator Validation des claims OIDC amr et acr src/auth/validators/mfa-claims.validator.ts
DeviceTrustService Gestion du cycle de vie de confiance des devices src/auth/services/device-trust.service.ts
DeviceTrustRepository Persistance des devices de confiance src/auth/repositories/device-trust.repository.ts
DeviceFingerprintExtractor Extraction du fingerprint depuis la requête src/auth/extractors/device-fingerprint.extractor.ts
SensitiveActionGuard Guard pour les actions sensibles (step-up MFA) src/auth/guards/sensitive-action.guard.ts
SensitiveActionsRegistry Registre des actions classées sensibles src/auth/config/sensitive-actions.registry.ts
MfaRequiredGuard Guard vérifiant l'exigence MFA selon contexte src/auth/guards/mfa-required.guard.ts
AuthSessionEnricher Enrichissement de la session avec contexte MFA/device src/auth/middleware/auth-session-enricher.middleware.ts
SecurityEventEmitter Émission d'événements de révocation src/auth/events/security-event.emitter.ts

2. Flux techniques

2.1 Flux nominal : Authentification avec nouveau device

┌─────────┐     ┌─────────┐     ┌──────────────┐     ┌─────────────────┐     ┌───────────────────┐
│ Client  │────▶│  IdP    │────▶│ ProbatioVault│────▶│ MfaClaimsValid. │────▶│ DeviceTrustService│
└─────────┘     └─────────┘     └──────────────┘     └─────────────────┘     └───────────────────┘
     │               │                  │                     │                        │
     │  1. Login     │                  │                     │                        │
     │──────────────▶│                  │                     │                        │
     │               │                  │                     │                        │
     │  2. MFA Challenge (TOTP)         │                     │                        │
     │◀──────────────│                  │                     │                        │
     │               │                  │                     │                        │
     │  3. TOTP Code │                  │                     │                        │
     │──────────────▶│                  │                     │                        │
     │               │                  │                     │                        │
     │  4. Token OIDC (amr:["otp"])     │                     │                        │
     │◀──────────────│                  │                     │                        │
     │               │                  │                     │                        │
     │  5. Request + Token + Fingerprint│                     │                        │
     │─────────────────────────────────▶│                     │                        │
     │               │                  │  6. Validate claims │                        │
     │               │                  │────────────────────▶│                        │
     │               │                  │                     │  7. Check amr/acr      │
     │               │                  │                     │────────────────────────│
     │               │                  │                     │  OK: amr contains "otp"│
     │               │                  │◀────────────────────│                        │
     │               │                  │                     │                        │
     │               │                  │  8. Check device trust                       │
     │               │                  │─────────────────────────────────────────────▶│
     │               │                  │                     │  9. New device detected│
     │               │                  │                     │  10. MFA valid → Trust │
     │               │                  │◀─────────────────────────────────────────────│
     │               │                  │                     │                        │
     │  11. Session accepted            │                     │                        │
     │◀─────────────────────────────────│                     │                        │

2.2 Flux nominal : Authentification depuis device de confiance

┌─────────┐     ┌──────────────┐     ┌───────────────────┐
│ Client  │────▶│ ProbatioVault│────▶│ DeviceTrustService│
└─────────┘     └──────────────┘     └───────────────────┘
     │                  │                      │
     │  1. Token (amr sans "otp") + FP         │
     │─────────────────▶│                      │
     │                  │  2. Check device     │
     │                  │─────────────────────▶│
     │                  │  3. Device trusted   │
     │                  │  (< 90 jours)        │
     │                  │◀─────────────────────│
     │                  │                      │
     │  4. Session accepted (pas de MFA requis)│
     │◀─────────────────│                      │

2.3 Flux : Action sensible (step-up MFA)

┌─────────┐     ┌──────────────┐     ┌────────────────────┐     ┌─────────┐
│ Client  │────▶│ ProbatioVault│────▶│ SensitiveActionGuard│────▶│   IdP   │
└─────────┘     └──────────────┘     └────────────────────┘     └─────────┘
     │                  │                      │                      │
     │  1. Action EXPORT_DATA                  │                      │
     │─────────────────▶│                      │                      │
     │                  │  2. Is sensitive?    │                      │
     │                  │─────────────────────▶│                      │
     │                  │  3. Yes, check MFA   │                      │
     │                  │◀─────────────────────│                      │
     │                  │  4. amr sans "otp"   │                      │
     │                  │  → Step-up required  │                      │
     │  5. 403 + MFA_STEP_UP_REQUIRED          │                      │
     │◀─────────────────│                      │                      │
     │                  │                      │                      │
     │  6. Re-auth avec MFA                    │                      │
     │────────────────────────────────────────────────────────────────▶│
     │  7. Token (amr:["otp"])                 │                      │
     │◀────────────────────────────────────────────────────────────────│
     │                  │                      │                      │
     │  8. Retry action + nouveau token        │                      │
     │─────────────────▶│                      │                      │
     │                  │  9. MFA valid        │                      │
     │  10. Action executed                    │                      │
     │◀─────────────────│                      │                      │

3. Mapping invariants → mécanismes

Invariant ID Exigence Mécanisme Composant Observable Risque
I1 Session validée par IdP Validation JWT PD-26 en amont, aucun fallback local JwtAuthGuard (PD-26) Rejet systématique si token invalide/absent Moyen : dépendance PD-26
I2 MFA obligatoire Exigence MFA sur nouveau device (R2) + actions sensibles (R7) MfaRequiredGuard, SensitiveActionGuard Logs de rejet avec raison MFA_REQUIRED Faible
I3 Aucun secret MFA dans PV Pas de champ TOTP/seed/QR dans modèles, API, logs Architecture (absence de code) Audit code + inspection logs/DB Faible : par conception
I4 Décision via claims OIDC Lecture exclusive de amr/acr depuis JWT MfaClaimsValidator Unit tests avec tokens variés Faible
I5 Confiance conditionnée à MFA Device trust créé uniquement si amr contient "otp" DeviceTrustService.grantTrust() DB : trusted_at non null ssi MFA Faible

4. Mapping critères d'acceptation → mécanismes

Critère ID Mécanisme(s) Composant Observable Risque
CA1 Validation amr contient "otp" + acr suffisant MfaClaimsValidator.validate() Réponse 200 + session créée Faible
CA2 Rejet si amr absent ou sans "otp" quand MFA requis MfaClaimsValidator.validate() Réponse 401/403 + code MFA_INVALID Faible
CA3 Lookup device → si inconnu, exiger MFA DeviceTrustService.isTrusted() + MfaRequiredGuard Log NEW_DEVICE_MFA_REQUIRED Moyen : fingerprint stability
CA4 Guard sur routes sensibles vérifiant MFA récent SensitiveActionGuard + SensitiveActionsRegistry Réponse 403 MFA_STEP_UP_REQUIRED Faible
CA5 Aucun endpoint/champ/log manipulant secrets MFA Revue architecture + tests d'inspection Grep codebase : 0 résultat pour patterns MFA secrets Faible : par conception

5. Mapping tests (TC-*) → mécanismes + observables

Test ID Référence spec Mécanisme(s) Point(s) d'observation Niveau de test
TC-NOM-01 R1, R2.a, I2 MfaRequiredGuard appliqué globalement Session acceptée ssi politique MFA respectée Integration
TC-NOM-02 R2, CA3 DeviceTrustService.isTrusted() retourne false pour nouveau device Log NEW_DEVICE, rejet sans MFA Integration
TC-NOM-03 R3, R3.a DeviceFingerprintExtractor + clé composite (client_id, fingerprint) DB : unicité du tuple, stabilité sur N requêtes Unit + Integration
TC-NOM-04 R4, R2.a, I5 DeviceTrustService.grantTrust() après MFA valide DB : device_trust.trusted_at non null Integration
TC-NOM-05 R5, R5.a, R5.b DeviceTrustService.isTrusted() vérifie last_auth_at + 90d > now Query SQL avec date IdP Integration
TC-NOM-06 R6 SecurityEventEmitterDeviceTrustService.revokeTrust() DB : revoked_at set, logs événement Integration
TC-NOM-07 R7, CA4 SensitiveActionGuard.canActivate() Réponse 403 si amr sans "otp" Integration
TC-NOM-08 R8 SensitiveActionsRegistry.isSensitive(action) Config/liste consultable Unit
TC-NOM-09 R9, R10, CA1, I4 MfaClaimsValidator.validate(token) Retour { valid: true } si amr.includes("otp") Unit
TC-NOM-10 R11, R12, R12.a MfaClaimsValidator.validateAcr(token) Rejet si acr !== "urn:probatiovault:acr:mfa" Unit
TC-SCOPE-01 §6 Absence d'endpoints recovery codes 404 sur /api/mfa/recovery-codes E2E
TC-ERR-01 E1, R9, CA2 MfaClaimsValidator throw si amr absent Exception MfaClaimMissingError Unit
TC-ERR-02 E3, R12 MfaClaimsValidator throw si acr insuffisant Exception AcrInsufficientError Unit
TC-ERR-03 E2, R10, CA2 MfaClaimsValidator throw si amr sans "otp" et MFA requis Exception MfaNotSatisfiedError Unit
TC-ERR-04 E4 SensitiveActionGuard rejette action sensible sans MFA Réponse 403 SENSITIVE_ACTION_MFA_REQUIRED Integration
TC-INV-01 I1 JwtAuthGuard (PD-26) en amont Rejet 401 si token invalide Integration
TC-INV-02 I2 Combinaison guards sur nouveau device Rejet si amr sans "otp" Integration
TC-INV-03 I3, CA5 Audit statique + runtime Grep + inspection logs/DB Security
TC-INV-04 I4 MfaClaimsValidator ne lit que amr/acr Code review + unit tests Unit
TC-INV-05 I5 DeviceTrustService.grantTrust() vérifie MFA Condition if (!mfaValid) throw Unit
TC-NR-01 I4 Idempotence de MfaClaimsValidator N appels → même résultat Unit
TC-NR-02 R8 Ajout action sensible n'affecte pas les autres Tests de non-régression Integration
TC-NEG-01 I1, I4 JwtAuthGuard rejette tokens forgés Réponse 401 Security
TC-NEG-02 R7, E4 SensitiveActionGuard sur token password-only Réponse 403 Integration
TC-NEG-03 R6 Post-révocation, MFA exigé DB check + rejet Integration

6. Gestion des erreurs

Code erreur Situation HTTP Message Observable
MFA_CLAIM_MISSING Claim amr absent du token 401 "Authentication method reference (amr) claim is required" Log auth.mfa.claim_missing
MFA_NOT_SATISFIED amr ne contient pas "otp" quand MFA requis 403 "Multi-factor authentication required" Log auth.mfa.not_satisfied
ACR_INSUFFICIENT acr présent mais ≠ urn:probatiovault:acr:mfa 403 "Authentication assurance level insufficient" Log auth.acr.insufficient
NEW_DEVICE_MFA_REQUIRED Nouveau device détecté, MFA requis 403 "MFA required for new device" Log auth.device.new_mfa_required
SENSITIVE_ACTION_MFA_REQUIRED Action sensible sans MFA récent 403 "MFA step-up required for this action" Log auth.sensitive.step_up_required
DEVICE_TRUST_EXPIRED Confiance device expirée (> 90j) 403 "Device trust expired, MFA required" Log auth.device.trust_expired
DEVICE_TRUST_REVOKED Confiance révoquée (logout/reset) 403 "Device trust revoked, MFA required" Log auth.device.trust_revoked

Structure de réponse d'erreur

interface MfaErrorResponse {
  statusCode: number;
  error: string;
  message: string;
  code: string;           // Code machine (e.g., "MFA_NOT_SATISFIED")
  mfaRequired: boolean;   // Indique si step-up MFA nécessaire
  redirectUri?: string;   // URI IdP pour re-auth MFA (optionnel)
}

7. Impacts sécurité

7.1 Risques et mitigations

Risque Impact Probabilité Mitigation
Fingerprint spoofing Usurpation device confiance Moyen Fingerprint inclut User-Agent + IP partiel + headers
Token replay Réutilisation token MFA Faible Expiration courte + JTI unique (PD-26)
Bypass garde sensible Accès action sans MFA Faible Décorateur obligatoire + tests exhaustifs
Fuite device_fingerprint Corrélation utilisateurs Faible Hash du fingerprint en DB
Race condition trust Grant trust sans MFA Très faible Transaction DB atomique

7.2 Journalisation sécurité

Événement Niveau Données loguées Rétention
MFA validation success INFO user_id, device_id, amr_methods 90 jours
MFA validation failure WARN user_id, device_id, reason, amr_methods 1 an
Device trust granted INFO user_id, device_id, fingerprint_hash 90 jours
Device trust revoked WARN user_id, device_id, revocation_reason 1 an
Sensitive action blocked WARN user_id, action, reason 1 an
Sensitive action allowed INFO user_id, action, mfa_timestamp 90 jours

7.3 Conformité

  • I3 : Aucun secret MFA (TOTP seed, QR, codes secours) n'est stocké/manipulé/loggé par ProbatioVault
  • RGPD : device_fingerprint hashé, pas de données biométriques
  • Auditabilité : Tous les événements MFA/device sont tracés (R-SP-04)

8. Hypothèses techniques

ID Hypothèse Impact si faux
H-01 L'IdP (Keycloak) est configuré pour inclure amr dans les tokens Rejet systématique de toutes sessions (E1)
H-02 L'IdP émet amr: ["otp"] uniquement après validation TOTP réelle Faux positifs MFA (violation I4)
H-03 Le claim acr si présent suit la convention urn:probatiovault:acr:* Rejets inattendus ou acceptations erronées
H-04 Le device_fingerprint fourni par le client est stable pour un même device Devices "nouveaux" en boucle, UX dégradée
H-05 PD-26 (validation JWT) est implémenté et fonctionnel Prérequis non satisfait, blocage complet
H-06 La référence temporelle IdP est synchronisée (NTP) Calcul 90 jours erroné
H-07 Les actions sensibles sont décorées explicitement par les développeurs Actions sensibles non protégées

9. Points de vigilance (risques, dette, pièges)

9.1 Risques techniques

Risque Description Mitigation
Fingerprint instable Changement User-Agent/IP → nouveau device Utiliser composants stables (client_id + hash navigateur)
Performance DB Lookup device trust sur chaque requête Index composite (user_id, client_id, fingerprint_hash)
Migration données Devices existants sans trust Migration : tous devices existants → non trusted
Coordination IdP Configuration Keycloak doit matcher Documentation + tests d'intégration

9.2 Dette technique anticipée

  • Liste actions sensibles : Hardcodée initialement, prévoir config externe
  • Expiration 90 jours : Valeur en constante, prévoir paramétrage
  • Step-up UX : Redirection IdP basique, améliorer avec modal in-app

9.3 Pièges d'implémentation

  1. Ne pas confondre amr (array) et acr (string)
  2. Vérifier inclusion : amr.includes("otp") et non amr === "otp"
  3. Atomicité : Grant trust dans transaction avec validation MFA
  4. Timezone : Toujours UTC pour calculs d'expiration
  5. Logs : Ne jamais logger le contenu complet du token (secrets potentiels)

10. Hors périmètre

Les éléments suivants sont explicitement exclus de PD-27 :

Élément Raison Propriétaire
Génération/validation secrets TOTP Délégué à l'IdP Keycloak
QR codes d'enregistrement MFA Délégué à l'IdP Keycloak
Codes de secours (backup codes) Délégué à l'IdP (§6) Keycloak
Mécanismes anti-bruteforce Délégué à l'IdP Keycloak
Seuils "tentatives infructueuses" Délégué à l'IdP (R6 note) Keycloak
Procédures de récupération compte Hors périmètre applicatif Support/Admin
Configuration initiale IdP Prérequis infrastructure Ops
Propriétés cryptographiques TOTP Hors périmètre (RFC 6238) Standard

Annexe : Schéma de données

Table device_trust

CREATE TABLE device_trust (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    user_id UUID NOT NULL REFERENCES users(id),
    client_id VARCHAR(255) NOT NULL,
    fingerprint_hash VARCHAR(64) NOT NULL,  -- SHA-256
    trusted_at TIMESTAMP WITH TIME ZONE,     -- NULL si jamais MFA validé
    last_auth_at TIMESTAMP WITH TIME ZONE NOT NULL,
    revoked_at TIMESTAMP WITH TIME ZONE,     -- NULL si actif
    revocation_reason VARCHAR(50),           -- 'logout_global', 'security_reset', 'admin_action'
    created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
    updated_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),

    CONSTRAINT uk_device_trust_user_device UNIQUE (user_id, client_id, fingerprint_hash)
);

CREATE INDEX idx_device_trust_user_lookup ON device_trust(user_id, client_id, fingerprint_hash)
    WHERE revoked_at IS NULL;

Document : PD-27-plan.md Version : 1.0 Statut : Draft Dépendances : PD-23, PD-26