-
feat : audit log (table + writer + listener + API + admin UI + timeline) (#9)
released this
2026-05-13 08:29:30 +00:00 Résumé
Implémente le journal d'audit append-only couvrant les 5 tickets de
doc/audit-log.mdet embarque au passage plusieurs corrections périphériques (sidebar Admin/Mon compte, drawer RBAC, Swagger, schema_filter Doctrine) ainsi que l'initialisation de la suite e2e Playwright. Toutes les mutations Doctrine sur les entités portant#[Auditable]sont tracées dans une table PostgreSQL dédiée, exposée en lecture seule via API Platform et consultable par les admins dans une page dédiée.Ce qui change
Audit log — cœur de la PR
Backend
- Migration : table
audit_log(UUID v7 natif Postgres en PK,jsonb changes, 3 index pour tri chrono, par entité et par utilisateur). AuditLogWriter: service bas-niveau, écrit via une connexion DBAL dédiéeaudit(même DSN quedefault, service séparé) pour sortir de la transaction ORM en batch. Blacklist defense-in-depthpassword/plainPassword/token/secret.RequestIdProvider: UUID v4 généré aukernel.requestprincipal, injecté dans chaque ligne d'audit de la requête.- Attributs
#[Auditable]/#[AuditIgnore]danssrc/Shared/Domain/Attribute/(accessibles par tous les modules). AuditListener: captureonFlush/ écriturepostFlushavec pattern swap-and-clear contre les flushes ré-entrants. Erreurs loguées, jamais propagées. EntitéUserannotée (password / plainPassword ignorés).- API Platform read-only
/api/audit-logs(permission RBACcore.audit_log.view) :GETcollection paginée +GETitem, pas de POST/PUT/PATCH/DELETE. Filtresentity_type,entity_id,action,performed_by,performed_at[after]/[before]. DbalPaginatorimplémentantPaginatorInterface:hydra:viewgénéré automatiquement par API Platform, pas de construction manuelle.- Ressource
AuditLogEntityTypesResource+ provider dédié pour peupler le filtre par type d'entité côté UI (réponse cachée, pas de requête à chaque ouverture du drawer). - Permission
core.audit_log.viewdéclarée dansCoreModule::permissions(). audit_logexclu duschema_filterDoctrine : plus de faux diff surmake migration-diff.
Frontend
- Page admin
/admin/audit-log: tableau paginé, filtres locaux (état dans le composant, non persistés dans l'URL — conforme règle CLAUDE.md « Tableaux : pas de persistance URL »), drawer de détail (diff + timeline complète de l'entité), badges colorés par action. - Composable partagé
useAuditLogavecresetAuditLog()auto-enregistré suronAuthSessionCleared(règle CLAUDE.md composables singletons). - Composant réutilisable
<AuditTimeline :entity-type :entity-id>: garde permission (pas d'appel API sans le droit), lazy loading (10 items + bouton « Voir plus »), dates relatives FR viaIntl.RelativeTimeFormat, skeleton loader. - Entrée sidebar « Journal d'audit » gated sur
core.audit_log.view+ clés i18n imbriquées dansfr.json.
Fixes embarqués
- Review fixes audit-log (commits
37eafd2,1505e84,99c77eb) : précision des timestamps,ESCAPEsur lesLIKE, plafond pagination, diverses remarques du 1er tour de review. - Sidebar (
701a480,e2fbf51) : nouvelle section « Administration » + groupe « Mon compte », gate de section sur permissions, « Tableau de bord » déplacé dans « Mon compte ». Convention admin documentée. - Drawer RBAC utilisateurs (
617ee31,5f5afcc) : corrige l'affichage des sites et l'écrasement via merge-patch (garde anti-écrasement + specGET /users/{id}/rbacdocumentée). - Swagger UI (
6db955f) : réactivé en ajoutantsymfony/twig-bundleaux deps (régression depuis l'arrivée d'API Platform 4.2). phpunit.dist.xml:<env APP_ENV=dev>forçait la suite à tourner sousframework.test=false(→test.service_containerintrouvable) ;JWT_PASSPHRASEne matchait pas les clés de dev. Corrigés pour débloquer la suite.
E2E Playwright (nouveau, commit
4603ab2)playwright.config.ts+ structurefrontend/tests/e2e/(personas, helpersloginAs, page objectsLoginPage+SidebarComponent).- Specs :
auth/login.spec.ts+permissions/sidebar-visibility.spec.ts(vérifie la visibilité de la sidebar par rôle RBAC). - Commande
SeedE2ECommandpour préparer un jeu de données déterministe côté backend. make e2eajouté au Makefile.
Décisions techniques
- UUID v7 natif Postgres (16 octets vs 36 en varchar) : index
performed_at~40 % plus petit sur une table append-only à croissance infinie. entity_typeformatmodule.Entity(ex:core.User) : évite les collisions si deux modules ont des entités de même nom.performed_bydénormalisé (string, pas FK) : le nom persiste même après suppression de l'utilisateur.- Connexion DBAL dédiée
audit: évite l'entanglement transactionnel entre audit et ORM en batch. ManyToManynon audité : limitation connue (getEntityChangeSet()ne couvre pas les collections) ; extension future viagetScheduledCollectionUpdates()si besoin.- Filtres locaux non persistés dans l'URL : choix assumé (cf. CLAUDE.md) pour éviter le couplage table ↔ routeur.
Test plan
make test: 218 tests passent (writer unitaires + listener intégration + API fonctionnels + UserRbacProcessor).npm run lint+npm run test+npm run build(frontend).- Migration appliquée sur dev + test,
audit_logignoré parschema_filter. - Permissions synchronisées (
app:sync-permissions). - Swagger
/api/docsaccessible de nouveau. - Playwright :
make e2evert en local (login + sidebar-visibility). - Vérifier en local : création/modif/suppression d'un user apparaît dans
/admin/audit-log. - Vérifier : user sans
core.audit_log.view→ 403 sur l'endpoint + item absent de la sidebar. - Vérifier : expansion d'une ligne affiche la timeline de l'entité avec dates relatives FR.
- Vérifier : drawer RBAC utilisateur n'écrase plus la liste des sites au
PATCH.
Points d'attention pour le review
AuditListener: pattern swap-and-clear surpostFlush— relire la gestion des flushes ré-entrants.DbalPaginator: vérifier que l'absence d'Iteratorcustom ne casse pas la normalisation API Platform sur collections vides.UserRbacProcessor: logique merge-patch + garde anti-écrasement des sites (régression corrigée dans617ee31).- Playwright : nouvelle dépendance de dev, s'assurer que
make e2ene fait pas partie du pipeline CI par défaut (à brancher explicitement).
Co-authored-by: Matthieu mtholot19@gmail.com
Reviewed-on: #9
Co-authored-by: matthieu matthieu@yuno.malio.fr
Co-committed-by: matthieu matthieu@yuno.malio.frDownloads
- Migration : table