832751d1ed
Auto Tag Develop / tag (push) Successful in 9s
## Contexte Certains comptes sont **partagés** par plusieurs personnes (ex. compte « Usine »), y compris depuis des smartphones. Le journal d'activité ne stockait que le `username` → impossible de distinguer les intervenants. Cette PR ajoute un **contexte forensique automatique** à chaque entrée du journal. ## Ce qui est ajouté (capté automatiquement, sans friction utilisateur) - **Adresse IP** de la requête - **User-Agent brut** (borné à 1024 caractères) - **Libellé appareil lisible** dérivé du User-Agent : `Type · OS · Navigateur` (ex. `Mobile · Android · Chrome`) - **Identifiant d'appareil persistant** envoyé par le front (header `X-Device-Id`, stocké en `localStorage`, borné à 64 car.) — distingue les **appareils** derrière un compte partagé ## Implémentation - `UserAgentParser` (service maison, sans dépendance) — détection ordonnée OS/navigateur, testée - 4 colonnes **nullable** sur `audit_logs` + migration réversible (pas de backfill, rétro-compatible) - Capture **centralisée** dans `AuditLogger::log()` via `RequestStack` — aucun processor modifié - Champs exposés dans l'API lecture (`AuditLogProvider` + DTO TS aligné) via `AuditLogReadRepositoryInterface` (suit le pattern existant des autres read-repos) - Front : `useDeviceId` + injection du header `X-Device-Id` dans `useApi` (sur toutes les requêtes, SSR-safe) - `framework.trusted_proxies` documenté (commenté) pour une IP correcte derrière un reverse proxy - Docs : `doc/audit-logging.md` + `CLAUDE.md` ## Hors périmètre (étapes suivantes) - **Écran du journal (`audit-logs.vue`) non modifié** — l'affichage des nouvelles colonnes fera l'objet d'une refonte séparée. Les données sont prêtes côté API. - La doc in-app (`documentation-content.ts`) n'est pas touchée : le journal est un outil caché `ROLE_SUPER_ADMIN` sans article existant ni niveau de doc super-admin. ## À noter pour le déploiement - L'IP n'est fiable derrière un reverse proxy qu'une fois `framework.trusted_proxies` activé (livré commenté). ## Tests `OK (249 tests, 533 assertions)` — sortie PHPUnit propre (aucune notice). 🤖 Generated with [Claude Code](https://claude.com/claude-code) Reviewed-on: #33 Co-authored-by: tristan <tristan@yuno.malio.fr> Co-committed-by: tristan <tristan@yuno.malio.fr>
70 lines
4.0 KiB
Markdown
70 lines
4.0 KiB
Markdown
# Journal des actions (Audit Log)
|
|
|
|
## Objectif
|
|
|
|
Tracer les actions utilisateurs pour diagnostiquer rapidement les problèmes de calcul signalés.
|
|
Quand un utilisateur signale une incohérence dans ses heures, RTT ou congés, le journal permet de voir
|
|
exactement ce qui a été modifié, par qui, et quand.
|
|
|
|
## Accès
|
|
|
|
- **Rôle requis** : `ROLE_SUPER_ADMIN` (rôle caché, non visible dans l'interface de gestion des utilisateurs)
|
|
- **Ajout du rôle** : directement en base de données
|
|
```sql
|
|
UPDATE users SET roles = '["ROLE_ADMIN","ROLE_SUPER_ADMIN"]' WHERE username = 'xxx';
|
|
```
|
|
- **Page** : `/audit-logs` (lien "Journal" dans la sidebar, visible uniquement avec le rôle)
|
|
|
|
## Actions tracées
|
|
|
|
| Processor | Entité | Actions |
|
|
|---|---|---|
|
|
| `AbsenceWriteProcessor` | Absence | create, delete |
|
|
| `WorkHourBulkUpsertProcessor` | WorkHour | create, update, delete |
|
|
| `WorkHourSiteValidationProcessor` | WorkHour | site_validate |
|
|
| `WorkHourBulkValidationProcessor` | WorkHour | validate |
|
|
| `WorkHourBulkSiteValidationProcessor` | WorkHour | site_validate |
|
|
| `EmployeeWriteProcessor` | Employee | create, update (changement contrat) |
|
|
| `ContractSuspensionWriteProcessor` | ContractSuspension | create, update |
|
|
| `EmployeeRttPaymentProcessor` | EmployeeRttPayment | update |
|
|
| `EmployeeFractionedDaysProcessor` | EmployeeLeaveBalance | update |
|
|
|
|
## Données stockées
|
|
|
|
Chaque entrée contient :
|
|
- **employee** : l'employé concerné (FK, nullable)
|
|
- **username** : l'utilisateur qui a effectué l'action
|
|
- **action** : type d'action (create, update, delete, validate, site_validate)
|
|
- **entityType** : type d'entité (work_hour, absence, employee, etc.)
|
|
- **description** : description lisible en français
|
|
- **changes** : diff JSON `{old: {...}, new: {...}}` avec les anciennes/nouvelles valeurs
|
|
- **affectedDate** : date de travail ou début d'absence (pour filtrage par période)
|
|
- **createdAt** : horodatage de l'action
|
|
- `ipAddress` : IP source de la requête (`Request::getClientIp()`) — nécessite `framework.trusted_proxies` derrière un reverse proxy, sinon IP du proxy
|
|
- `userAgent` : User-Agent brut de la requête
|
|
- `deviceLabel` : libellé lisible dérivé du User-Agent (`Type · OS · Navigateur`, ex. `Mobile · Android · Chrome`), via `App\Service\UserAgentParser`
|
|
- `deviceId` : identifiant d'appareil persistant envoyé par le front (header `X-Device-Id`, stocké en `localStorage['sirh-device-id']`). Distingue les **appareils** derrière un compte partagé (ex. « Usine »), pas les personnes.
|
|
|
|
Capture : automatique et centralisée dans `AuditLogger::log()` (via `RequestStack`) — aucun processor à modifier. En contexte CLI/cron (pas de requête), ces 4 champs restent `null`.
|
|
|
|
> ⚠️ **CORS** : le front et l'API sont sur des origines distinctes ; le header `X-Device-Id` ajouté à chaque requête déclenche un préflight CORS. Il **doit** figurer dans `nelmio_cors.allow_headers` (`config/packages/nelmio_cors.yaml`), sinon le navigateur bloque toutes les requêtes API.
|
|
|
|
## Filtres disponibles
|
|
|
|
- Par employé (affecté) — champ texte, recherche partielle sur nom/prénom (insensible à la casse)
|
|
- Par période (date affectée) — sélecteur de plage
|
|
- Par type(s) d'entité (multi-sélection)
|
|
- Par action(s) (multi-sélection)
|
|
- Par utilisateur / compte — champ texte, recherche partielle (insensible à la casse)
|
|
- Par IP (recherche partielle)
|
|
- Par appareil (recherche partielle sur le libellé ou le device id)
|
|
|
|
Pagination : `perPage` (10 / 25 / 50 / 100, défaut 10) + `page`.
|
|
|
|
L'écran utilise un `MalioDataTable`, un **drawer de filtre** (bouton « Filtrer » avec compteur de filtres actifs, état brouillon/appliqué, Réinitialiser/Appliquer) et un **drawer de détail** ouvert au clic sur une ligne (méta + contexte technique IP/appareil/User-Agent/device id + diff lisible des changements).
|
|
|
|
## Convention
|
|
|
|
Tout nouveau processor traitant des entités impactant les calculs (heures, absences, contrats, RTT)
|
|
doit intégrer le service `AuditLogger` et logger les actions create/update/delete.
|