---
title: "Journal d'audit"
description: "Toute action mutante (POST / PUT / PATCH / DELETE) sur /admin/* est journalisée automatiquement par AdminAuditMiddleware, monté au niveau du groupe admin —…"
---

Toute action **mutante** (`POST` / `PUT` / `PATCH` / `DELETE`) sur `/admin/*` est journalisée automatiquement par [AdminAuditMiddleware](../src/Http/Middleware/AdminAuditMiddleware.php), monté au niveau du **groupe** admin — aucune action n'a besoin d'être modifiée. Les `GET` (lectures : stats, santé, listes) ne sont **pas** tracés pour ne pas noyer le signal.

Le middleware enveloppe toute la pile : il observe le **statut final**, y compris les `403` d'auth refusée (un essai non autorisé est aussi un événement de sécurité conservé).

**Stockage** : base **SQLite locale** `var/admin_audit.sqlite` (hors MySQL), via [AdminAuditLogger](../src/Infrastructure/Audit/AdminAuditLogger.php). Choix délibéré : zéro dépendance, fichier unique archivable, **requêtable** (contrairement à un log plat), et fonctionne même si MySQL est injoignable. Écriture **best-effort** : un échec d'audit n'interrompt jamais l'action admin (erreur envoyée dans `error_log`).

**Schéma** (table `admin_audit`, créée automatiquement au premier write) :

| Colonne | Type | Description |
|---|---|---|
| `id` | INTEGER PK | auto-incrément |
| `created_at` | TEXT | ISO 8601 |
| `method` | TEXT | `POST` / `PUT` / `PATCH` / `DELETE` |
| `path` | TEXT | chemin appelé (ex. `/admin/media/{hex}/published`) |
| `query` | TEXT | query string (ou `null`) |
| `status` | INTEGER | statut HTTP final (inclut les `403`) |
| `ip` | TEXT | `REMOTE_ADDR` (pas de `X-Forwarded-For`) |
| `token_fp` | TEXT | **empreinte** du token (12 hex de SHA-256), jamais le token brut — `null` si absent |
| `user_agent` | TEXT | en-tête `User-Agent` (ou `null`) |

Le `token_fp` permet de corréler les actions d'un même appelant sans stocker le secret. Quand des tokens par opérateur existeront, l'empreinte pointera vers un opérateur nommé.

**Consulter le journal** : via l'API, l'endpoint [`GET /admin/audit`](#get-adminaudit) (filtres + pagination keyset). Ou en lecture directe SQLite (ex. les 20 dernières actions) :

```bash
sqlite3 var/admin_audit.sqlite \
  "SELECT created_at, method, path, status, ip, token_fp FROM admin_audit ORDER BY id DESC LIMIT 20;"
```

---
