---
title: "RGPD / suppression de compte"
description: "Trois endpoints couvrent les droits RGPD self-service de l'utilisateur : portabilité (export de ses données) et droit à l'effacement (suppression de compte…"
---

Trois endpoints couvrent les droits RGPD self-service de l'utilisateur : **portabilité** (export de ses données) et **droit à l'effacement** (suppression de compte). La suppression suit un modèle **soft-delete + période de grâce** : le compte est marqué supprimé immédiatement (et désactivé), puis effacé physiquement par un cron au-delà de la fenêtre de grâce. Tant que la grâce n'est pas écoulée, **se reconnecter réactive automatiquement le compte** (la réactivation est centralisée dans `SessionService::create()`).

| Variable d'environnement | Défaut | Rôle |
|--------------------------|--------|------|
| `ACCOUNT_DELETION_GRACE_DAYS` | `30` | Jours pendant lesquels un compte soft-deleted reste restaurable avant purge irréversible. |
| `ACCOUNT_DELETION_TOKEN_TTL_HOURS` | `24` | Validité du token e-mail de confirmation (comptes OAuth sans mot de passe). |
| `ACCOUNT_PURGE_BATCH_SIZE` | `50` | Comptes traités par tick de `bin/account-purge.php`. |
| `ACCOUNT_PURGE_MEDIA_BATCH_SIZE` | `100` | Médias effacés par page lors de la purge d'un compte. |

**Purge** (`bin/account-purge.php`, à planifier quotidiennement) : pour chaque compte dont la grâce est écoulée, tous les médias possédés sont supprimés (fichiers + DB + Meilisearch via `MediaDeleteService`), la ligne `user` est **anonymisée** (e-mail/username scrambés, champs PII nullés, `purged_at` horodaté) plutôt que physiquement supprimée — pour éviter un CASCADE FK non maîtrisé sur une table `user` seedée en externe — et le document Meilisearch `users` est retiré. L'opération est idempotente.

**Limitation connue** : pendant la période de grâce, le document Meilisearch `users` n'est **pas** retiré (il appartient à un pipeline externe et ne pourrait pas être recréé en cas de réactivation) ; seul l'authentification est bloquée. Le document n'est supprimé qu'à la purge définitive.

---

## `GET /api/users/me/export`

Export de portabilité RGPD : renvoie l'intégralité des données de l'utilisateur authentifié sous forme de fichier JSON téléchargeable (et **non** une ressource JSON:API — la charge utile est une archive, pas une entité d'API).

- **Auth** : requise (Bearer)
- **Action** : [ExportMyDataAction](../src/Http/Action/Api/Users/ExportMyDataAction.php)
- **Réponse `200 OK`** :
  - `Content-Type: application/json; charset=utf-8`
  - `Content-Disposition: attachment; filename="hydrogen-export-<userHex>-<YYYYMMDD>.json"`
- **Structure du corps** :

```json
{
  "generatedAt": "2026-06-16T12:00:00+00:00",
  "profile":  { "id": "…", "username": "…", "email": "…", "...": "tous les champs du profil" },
  "activity": { "followers": 0, "following": 0, "medias": 0, "albums": 0, "comments": 0 },
  "medias":   [ { "id": "…", "name": "…", "type": "image", "isPublished": true, "latitude": null, "longitude": null, "shotAt": null, "createdAt": "…" } ]
}
```

---

## `DELETE /api/users/me`

Demande de suppression du compte authentifié.

- **Auth** : requise (Bearer)
- **Action** : [RequestAccountDeletionAction](../src/Http/Action/Api/Users/RequestAccountDeletionAction.php)
- **Corps** (optionnel, plat ou JSON:API) :

```json
{ "currentPassword": "<mot de passe actuel>" }
```

- **Comportement selon le type de compte** :
  - **Compte avec mot de passe** : `currentPassword` est **obligatoire**. Le compte est soft-deleted immédiatement et toutes ses sessions sont révoquées.
  - **Compte OAuth-only** (sans mot de passe) : aucun mot de passe à vérifier ; un e-mail de confirmation contenant un lien à usage unique est envoyé. La suppression n'est effective qu'après [`POST /api/account/deletion/confirm`](#post-apiaccountdeletionconfirm).
- **Réponses** :
  - `200 OK` — compte soft-deleted (`status: "deleted"`). Si déjà en attente de suppression : `meta.alreadyPending = true`.
  - `202 Accepted` — e-mail de confirmation envoyé (compte OAuth-only).
  - `401 Unauthorized` — `currentPassword` manquant (`meta.code = "account.passwordRequired"`) ou invalide (`meta.code = "account.invalidPassword"`).

---

## `POST /api/account/deletion/confirm`

Confirme une suppression de compte via le token reçu par e-mail — chemin de **fallback** pour les comptes OAuth-only sans mot de passe.

- **Auth** : aucune (le token autorise l'action). **POST** volontaire (jamais GET) afin qu'un prefetch de lien par un scanner d'e-mail ne déclenche pas une suppression irréversible.
- **Action** : [ConfirmAccountDeletionAction](../src/Http/Action/Api/Account/ConfirmAccountDeletionAction.php)
- **Corps** (plat ou JSON:API) :

```json
{ "token": "<token base64url issu du lien e-mail>" }
```

- **Réponses** :
  - `200 OK` — compte soft-deleted, période de grâce démarrée (`status: "deleted"`, `confirmedAt`).
  - `410 Gone` — token inconnu, expiré ou déjà utilisé (`meta.code = "account.deletionTokenInvalid"`).
  - `422 Unprocessable Entity` — `token` absent du corps.

---
