PD-240 — Plan d'implémentation¶
1. Références¶
- Spec : PD-240-specification.md (v3)
- Tests : PD-240-tests.md (v3)
- Verdict Gate 3 : GO v3
2. Découpage en composants¶
2.1 Composants à créer¶
| Composant | Responsabilité | Agent |
|---|---|---|
| DeleteAccountController | Endpoint REST DELETE /user/account | agent-developer |
| DeleteAccountService | Orchestration du flux de suppression | agent-developer |
| SessionInvalidationService | Invalidation des sessions Redis | agent-developer |
| UserDataPurgeService | Anonymisation/purge RGPD | agent-developer |
| KeycloakUserService | Suppression compte Keycloak | agent-developer |
| AuditController | Endpoint admin GET /admin/audit/user/{userId} | agent-developer |
| DeleteAccountE2ETests | Tests d'intégration | agent-qa-unit-integration |
2.2 Composants existants à modifier¶
| Composant | Modification | Agent |
|---|---|---|
| ReauthMiddleware (PD-238) | Aucune modification, réutilisation | — |
| JwtAuthGuard | Aucune modification, réutilisation | — |
3. Flux de traitement¶
3.1 Flux nominal (F-240-01)¶
Client Controller Service External
| | | |
|-- DELETE /user/account | | |
| + JWT | | |
| + X-Reauth-Token | | |
| + body: {confirm} | | |
| | | |
| |-- validateJWT() ---| |
| |-- validateReauth() | |
| |-- validateConfirm()| |
| | | |
| |-- deleteAccount() -----------------> |
| | | |
| | |-- invalidateSessions()
| | | | |
| | | |-- Redis.del() -|
| | | |
| | |-- purgeUserData() -|
| | | | |
| | | |-- PostgreSQL |
| | | |
| | |-- deleteKeycloakUser()
| | | | |
| | | |-- Keycloak API |
| | | |
|<-- 200 OK -------------| | |
3.2 Ordre transactionnel¶
- Validation (pré-conditions)
- JWT valide (JwtAuthGuard)
- Reauth token valide (ReauthMiddleware)
-
Confirmation renforcée valide (
confirm === "DELETE_MY_ACCOUNT") -
Invalidation sessions (point de non-retour si succès)
- Supprimer toutes les clés Redis
session:{userId}:* -
Si échec → rollback, retourner ERR-240-SESSION-INVALIDATION-FAILED
-
Purge RGPD
- Anonymiser table
users(email, name) - Purger tables liées (à définir selon schéma)
- Créer entrée audit
{ status: "purged", purgedAt } -
Si échec → ERR-240-DELETE-FAILED (sessions déjà invalidées, compte non supprimé)
-
Suppression Keycloak
- Appeler
DELETE /admin/realms/{realm}/users/{keycloakId} -
Si échec → ERR-240-DELETE-FAILED
-
Réponse succès
- Retourner
200 OKavec body vide ou{ success: true }
4. Diagrammes Mermaid¶
4.1 Graphe de dépendances des composants¶
graph TD
subgraph "Couche HTTP"
DAC[DeleteAccountController]
AC[AuditController]
end
subgraph "Guards & Middleware"
JAG[JwtAuthGuard]
RAM[ReauthMiddleware<br/><i>PD-238</i>]
end
subgraph "Couche Services"
DAS[DeleteAccountService]
SIS[SessionInvalidationService]
UDPS[UserDataPurgeService]
KUS[KeycloakUserService]
end
subgraph "Externes"
Redis[(Redis)]
PG[(PostgreSQL)]
KC[Keycloak API]
end
DAC -->|"protège"| JAG
DAC -->|"protège"| RAM
DAC -->|"appelle"| DAS
AC -->|"protège"| JAG
DAS -->|"1. invalidate"| SIS
DAS -->|"2. purge"| UDPS
DAS -->|"3. delete"| KUS
SIS -->|"DEL session:userId:*"| Redis
UDPS -->|"anonymise + audit"| PG
KUS -->|"DELETE /users/keycloakId"| KC
AC -->|"SELECT audit"| PG 4.2 Diagramme de séquence — Flux nominal (F-240-01)¶
sequenceDiagram
actor Client
participant Ctrl as DeleteAccountController
participant Guard as JwtAuthGuard
participant Reauth as ReauthMiddleware<br/>(PD-238)
participant Svc as DeleteAccountService
participant Session as SessionInvalidationService
participant Purge as UserDataPurgeService
participant KC as KeycloakUserService
participant Redis as Redis
participant PG as PostgreSQL
participant KCApi as Keycloak API
Client->>Ctrl: DELETE /user/account<br/>JWT + X-Reauth-Token<br/>body: {confirm}
Ctrl->>Guard: validateJWT()
Guard-->>Ctrl: userId
Ctrl->>Reauth: validateReauth()
Reauth-->>Ctrl: OK
Ctrl->>Ctrl: validateConfirm("DELETE_MY_ACCOUNT")
Ctrl->>Svc: deleteAccount(userId)
activate Svc
Svc->>Session: invalidateSessions(userId)
activate Session
Session->>Redis: DEL session:{userId}:*
Redis-->>Session: OK
Session-->>Svc: sessions invalidées
deactivate Session
Svc->>Purge: purgeUserData(userId)
activate Purge
Purge->>PG: UPDATE users SET email=anon, name=anon
Purge->>PG: INSERT audit_log (status: purged)
PG-->>Purge: OK
Purge-->>Svc: données purgées
deactivate Purge
Svc->>KC: deleteKeycloakUser(keycloakId)
activate KC
KC->>KCApi: DELETE /admin/realms/{realm}/users/{id}
KCApi-->>KC: 204 No Content
KC-->>Svc: OK
deactivate KC
deactivate Svc
Ctrl-->>Client: 200 OK 4.3 Diagramme de séquence — Échec purge après invalidation sessions¶
sequenceDiagram
actor Client
participant Ctrl as DeleteAccountController
participant Svc as DeleteAccountService
participant Session as SessionInvalidationService
participant Purge as UserDataPurgeService
participant Redis as Redis
participant PG as PostgreSQL
Client->>Ctrl: DELETE /user/account
Ctrl->>Svc: deleteAccount(userId)
Svc->>Session: invalidateSessions(userId)
Session->>Redis: DEL session:{userId}:*
Redis-->>Session: OK
Session-->>Svc: sessions invalidées
Svc->>Purge: purgeUserData(userId)
Purge->>PG: UPDATE users ...
PG-->>Purge: ERROR (constraint violation)
Purge-->>Svc: échec
Note over Svc: État dégradé :<br/>sessions mortes,<br/>Keycloak intact.<br/>Audit log + job réconciliation.
Svc-->>Ctrl: ERR-240-DELETE-FAILED
Ctrl-->>Client: 500 Internal Server Error 5. Mapping Invariants → Mécanismes¶
| Invariant | Mécanisme technique | Observable |
|---|---|---|
| INV-240-01 | JwtAuthGuard sur route | 401 si JWT absent/invalide |
| INV-240-02 | ReauthMiddleware (PD-238) | 403 si reauth token invalide |
| INV-240-03 | Validation body confirm === "DELETE_MY_ACCOUNT" | 400 ERR-240-DELETE-CONFIRMATION |
| INV-240-04 | Keycloak Admin API DELETE | Échec auth ultérieure |
| INV-240-05 | PostgreSQL UPDATE/DELETE + audit entry | Endpoint /admin/audit/user/{userId} |
| INV-240-06 | Redis DEL avant suppression Keycloak | Tokens précédents refusés |
| INV-240-07 | Combinaison INV-240-04 + INV-240-06 | Compte inutilisable |
| INV-240-08 | ExceptionFilter avec format {error, message} | Réponses JSON conformes |
| INV-240-09 | Messages d'erreur explicites dans ExceptionFilter | Message actionnable |
6. Mapping Critères d'acceptation → Tests¶
| CA | Test | Mécanisme vérifié |
|---|---|---|
| CA-240-01 | T-240-ERR-01 | JwtAuthGuard |
| CA-240-02 | T-240-ERR-02 | ReauthMiddleware |
| CA-240-03 | T-240-ERR-03 | Validation confirmation |
| CA-240-04 | T-240-POST-01 | Keycloak DELETE |
| CA-240-05 | T-240-POST-02 | Endpoint admin /admin/audit/user/{userId} |
| CA-240-06 | T-240-NOM-01 | Redis DEL |
| CA-240-07 | T-240-ERR-* | ExceptionFilter format |
| CA-240-08 | T-240-ERR-04/05/06 | Messages explicites |
7. Gestion des erreurs¶
| Code | Condition | HTTP | Implémentation |
|---|---|---|---|
| ERR-240-UNAUTHENTICATED | JWT absent/invalide | 401 | JwtAuthGuard exception |
| ERR-240-UNAUTHORIZED-REAUTH | Reauth token invalide | 403 | ReauthMiddleware exception |
| ERR-240-DELETE-CONFIRMATION | confirm !== "DELETE_MY_ACCOUNT" | 400 | Controller validation |
| ERR-240-DELETE-FAILED | Échec purge ou Keycloak | 500 | Service exception avec cause |
| ERR-240-SESSION-INVALIDATION-FAILED | Échec Redis | 500 | Service exception + rollback |
| ERR-240-INTERNAL | Autre erreur | 500 | Global ExceptionFilter |
8. Sécurité¶
8.1 Contrôles d'accès¶
- Route protégée par JwtAuthGuard (authentification)
- ReauthMiddleware exige une re-authentification récente (< 5 min)
- Confirmation explicite empêche suppression accidentelle
8.2 Données sensibles¶
- Les tokens JWT ne sont jamais loggés
- Les données purgées sont anonymisées, pas supprimées (audit trail)
- L'endpoint admin est protégé par rôle ADMIN
8.3 Atomicité¶
- L'ordre (sessions → purge → Keycloak) garantit qu'aucune session ne survit à une suppression réussie
- Si échec sessions → rollback complet
- Si échec purge/Keycloak après invalidation sessions → compte verrouillé (sessions mortes, Keycloak intact) — état dégradé acceptable
9. Hypothèses techniques¶
| ID | Hypothèse | Validation |
|---|---|---|
| HT-01 | Keycloak Admin API accessible depuis le backend | Test de connexion au démarrage |
| HT-02 | Redis accessible et contient les sessions au format session:{userId}:* | Conforme à PD-30 |
| HT-03 | Le schéma PostgreSQL a une table users avec email, name | Conforme à PD-23 |
| HT-04 | ReauthMiddleware de PD-238 est disponible et fonctionnel | PD-238 DONE |
10. Points de vigilance¶
-
Rollback limité : Si la purge RGPD échoue après invalidation des sessions, le compte est dans un état "limbo" (sessions mortes mais Keycloak actif). Mitigation : logs d'audit + job de réconciliation.
-
Performance : La purge RGPD peut impacter plusieurs tables. Considérer une exécution asynchrone pour les tables volumineuses, mais garantir l'atomicité sur les tables critiques.
-
Keycloak latence : La suppression Keycloak est synchrone. S'assurer du timeout approprié.
11. Livrables¶
| Fichier | Description |
|---|---|
src/modules/user/controllers/delete-account.controller.ts | Endpoint REST |
src/modules/user/services/delete-account.service.ts | Orchestration |
src/modules/user/services/session-invalidation.service.ts | Invalidation Redis |
src/modules/user/services/user-data-purge.service.ts | Purge RGPD |
src/modules/auth/services/keycloak-user.service.ts | Intégration Keycloak |
src/modules/admin/controllers/audit.controller.ts | Endpoint admin |
src/modules/user/dto/delete-account.dto.ts | DTO confirmation |
test/e2e/delete-account.e2e-spec.ts | Tests E2E |