Skip to content

GDPR self-service (export / erasure)

Repod exposes two GDPR self-service endpoints for the personal data tied to a user account: Article 15 (right of access — export) and Article 17 (right to erasure). Both are usable by the account owner directly, or by an administrator acting on another account.


1. Prerequisites

  • A logged-in Repod session. No special role is required to export or erase your own data.
  • To act on someone else's account, the caller must have the admin role.

2. Article 15 — Export your own data

Step 1 — Call the export endpoint

curl -H "Authorization: Bearer $JWT" \
  https://repod.example.com/api/v1/auth/users/alice/export
{
  "user": {
    "username": "alice",
    "full_name": "Alice Martin",
    "email": "alice@example.com",
    "role": "maintainer",
    "created_at": "2025-11-02 09:14:00",
    "last_login": "2026-08-19 17:03:11",
    "auth_source": "local",
    "mfa_enabled": true
  },
  "api_tokens": [
    {"name": "ci-runner", "role": "uploader", "created_at": "...", "expires_at": null, "last_used": "..."}
  ],
  "audit_entries": [
    {"timestamp": "...", "action": "UPLOAD", "result": "SUCCESS", "user": "alice", "package": "nginx", "detail": "..."}
  ],
  "decisions": [
    {"package": "openssl", "version": "3.0.2", "action": "accept_risk", "justification": "...", "decided_at": "..."}
  ],
  "groups": [
    {"name": "security-team", "added_at": "..."}
  ]
}

The export covers: account profile fields, API tokens you created, every audit-log entry where you are the acting user, CVE decisions you recorded, and group memberships. It does not include your password hash or MFA secret.

Who can call this

requesting_user["username"] == username (exporting your own data) or requesting_user["role"] == "admin" (an admin exporting anyone's data). Any other combination returns 403.


3. Article 17 — Erase your own data (self-service)

Erasure anonymizes the account rather than deleting the row outright — this preserves the legally required audit trail while removing every personally identifying field.

Step 1 — Call the erasure endpoint with your password

curl -X DELETE https://repod.example.com/api/v1/auth/users/alice/gdpr \
  -H "Authorization: Bearer $JWT" \
  -H "Content-Type: application/json" \
  -d '{"password": "your-current-password"}'
{
  "status": "ok",
  "anonymized_as": "anonymized_a1b2c3d4",
  "message": "Données personnelles de alice anonymisées conformément au RGPD Art. 17."
}

Irreversible, and ends your own session

Self-erasure requires your current password as confirmation because it is irreversible — your username, full name, email, and password hash are wiped, your account is deactivated, your API tokens are deleted, and your existing JWT stops working (the account it refers to no longer authenticates). There is no undo.

What erasure does, precisely

  • users row: usernameanonymized_XXXXXXXX (random), full_name/email cleared, active = false, hashed_password cleared, TOTP secret/pending secret/recovery codes all cleared (MFA is fully wiped too).
  • api_tokens created by the account: deleted outright.
  • decision_records.decided_by: rewritten to the same anonymized identifier (the fact that a CVE decision was made is preserved for audit purposes; the identity behind it is not).
  • group_members.username: rewritten to the same anonymized identifier.
  • Audit log entries are never deleted or modified — legal traceability requirement. They still reference the original username as it was recorded at the time; this is a documented, accepted characteristic of the anonymization (past audit history is a historical record, not personal data actively linked to a live account going forward).

Who can call this

  • The account owner (is_self), with mandatory password confirmation in the request body.
  • An admin acting on a different account — no password confirmation required (same authority convention as the regular delete_user() admin action).

The one exception: admins cannot self-erase

{
  "detail": "Un administrateur ne peut pas supprimer son propre compte via cette voie (risque de verrouillage total du système) — demandez à un autre administrateur."
}

If the caller is an admin erasing their own account, the request is rejected with 400 regardless of password. This is a deliberate safety rail: erasing the last (or only) admin account would permanently lock the whole installation out of admin-level access, with no recovery path. An admin who wants their own data erased must ask a second administrator to perform the erasure on their behalf.


4. Article 17 — Admin erasing another user's account

curl -X DELETE https://repod.example.com/api/v1/auth/users/bob/gdpr \
  -H "Authorization: Bearer $ADMIN_JWT"

No request body is required — an admin acting on someone else's account skips the password-confirmation step entirely. The response shape is identical to the self-service case.


5. Verify it worked

  1. Export: confirm the JSON response includes recognizable data for the account (username, audit entries, tokens) and returns 403 when called by a non-admin, non-owner user.
  2. Self-erasure: after calling DELETE .../gdpr with your password, confirm your old JWT no longer authenticates (GET /api/v1/auth/me with the same token returns 401), and that GET /api/v1/auth/users (as an admin) shows the account under its new anonymized_XXXXXXXX username with active: false.
  3. Admin self-erasure block: as an admin, call DELETE /api/v1/auth/users/<your-own-username>/gdpr and confirm you get 400, not 200.
  4. Audit trail preserved: confirm GET /api/v1/audit/logs?action=GDPR_DELETE shows the erasure event itself, and that older entries recorded under the now-anonymized username are still present and unaltered.