Journal d'audit
Le journal d'audit est la memoire de la plateforme. Il repond a la question fondamentale : "Qui a fait quoi, quand et depuis ou ?" C'est un outil indispensable pour investiguer un incident de securite, verifier qu'une action a bien ete effectuee ou demontrer la conformite RGPD.
Chaque entree est horodatee, associee a un utilisateur et enrichie de metadonnees contextuelles incluant la geolocalisation IP. Le journal est immutable -- une fois enregistree, une entree ne peut etre ni modifiee ni supprimee.
La page est accessible depuis /backoffice/audit-log et utilise un datatable anime (Framer Motion) avec pagination serveur, recherche textuelle, export CSV complet et statistiques optimisees.
Types d'evenements
BeePass trace 19 types d'evenements couvrant l'authentification, la gestion de compte, l'administration et les operations metier. Ce perimetre a ete defini pour couvrir toutes les actions susceptibles d'avoir un impact sur la securite ou les donnees des utilisateurs.
Authentification (8 types)
Ces evenements tracent le cycle de vie de la session : de la connexion a la deconnexion, en passant par les verifications de securite.
| Type | Description | Source | Helper |
|---|---|---|---|
login_success | Connexion eleveur reussie (email + OAuth Google/Facebook) | AuthLogin.tsx, /auth/callback | logLoginSuccess() |
login_failed | Tentative de connexion echouee | AuthLogin.tsx | logLoginFailed() |
logout | Deconnexion volontaire | AuthContext | logLogout() |
password_change | Changement de mot de passe | SecurityTab.tsx | logPasswordChange() |
password_reset_request | Demande de reinitialisation (HMAC token + Brevo) | /api/auth/password-reset | logAuditServer() |
device_verification | Verification d'un nouvel appareil (MFA email) | Client | logDeviceVerification() |
session_revoked | Revocation d'une session active | /api/auth/sessions | insert direct |
mfa_enabled | Activation du 2FA TOTP | SecurityTab.tsx | logAuditEvent() |
Gestion de compte (2 types)
| Type | Description | Source | Helper |
|---|---|---|---|
mfa_disabled | Desactivation du 2FA TOTP (eleveurs uniquement) | SecurityTab.tsx | logAuditEvent() |
account_deletion | Suppression definitive d'un compte | /api/auth/delete-account | insert direct |
Administration (4 types)
Ces evenements sont specifiques aux actions des administrateurs. Ils permettent de repondre a des questions comme "Qui a banni cet utilisateur ?" ou "Qui a approuve ce changement de role ?"
| Type | Description | Source | Helper |
|---|---|---|---|
admin_login | Connexion au backoffice admin (succes + echec) | /api/admin/auth/login | logAuditServer() |
admin_logout | Deconnexion du backoffice admin | /api/admin/auth/logout | logAuditServer() |
role_change | Modification des roles d'un utilisateur (approve/reject) | /api/admin/roles | logAuditServer() |
user_banned | Bannissement ou debannissement d'un utilisateur | /api/admin/users/[id]/ban | logAuditServer() |
Operations metier (5 types)
Ces evenements tracent les actions qui modifient les donnees metier de la plateforme -- abonnements, reines importees, cles API.
| Type | Description | Source | Helper |
|---|---|---|---|
subscription_change | Changement de plan d'abonnement Stripe | /api/billing/webhook | insert direct |
queen_import | Import de reines (unitaire ou en masse) | /api/admin/reference-queens/import | logAuditServer() |
api_key_created | Creation d'une cle API | /api/api-keys POST | insert direct |
api_key_revoked | Revocation d'une cle API | /api/api-keys/[id] DELETE | insert direct |
profile_update | Modification du profil utilisateur | /api/auth/profile PUT | logAuditServer() |
Structure d'un evenement
Schema de la table auth_audit_log
Chaque evenement enregistre non seulement l'action elle-meme, mais aussi son contexte : depuis quel appareil, quel navigateur, quelle adresse IP. Ces informations sont cruciales pour differencier un acces legitime d'une intrusion.
| Colonne | Type | Description |
|---|---|---|
id | UUID | Identifiant unique de l'evenement |
user_id | UUID | FK vers auth.users (ON DELETE SET NULL -- conserve le log apres suppression) |
event_type | TEXT | Un des 19 types listes ci-dessus |
ip_address | TEXT | IP source de la requete |
user_agent | TEXT | Navigateur et systeme d'exploitation |
device_name | TEXT | Nom de l'appareil (extrait du User Agent) |
browser | TEXT | Navigateur (Chrome, Firefox, Safari...) |
success | BOOLEAN | Resultat de l'action (succes ou echec) |
metadata | JSONB | Donnees contextuelles specifiques au type d'evenement |
created_at | TIMESTAMPTZ | Horodatage UTC de l'evenement |
Metadonnees par type d'evenement
Le champ metadata (format JSONB) contient des donnees specifiques a chaque type d'evenement. C'est ce qui donne du contexte a un evenement brut -- par exemple, un role_change seul dit qu'un role a change, mais les metadonnees precisent quel role a ete ajoute ou retire, et pour quel utilisateur.
| Type | Champs metadata | Exemple |
|---|---|---|
login_success | method (email/google/facebook) | {"method": "email"} |
login_failed | reason | {"reason": "Invalid credentials"} |
password_change | -- | {} |
password_reset_request | email (masque) | {"email": "m***@beepass.io"} |
role_change | target_user_id, old_roles, new_roles, action | {"action": "approve", "new_roles": ["eleveur", "groupe_selection"]} |
user_banned | target_user_id, action | {"action": "ban", "target_user_id": "uuid"} |
subscription_change | old_plan, new_plan, status | {"old_plan": "discovery", "new_plan": "breeder", "status": "trialing"} |
queen_import | count, format, destination | {"count": 150, "format": "mellifera", "destination": "reference"} |
session_revoked | session_id | {"session_id": "uuid"} |
mfa_enabled | method | {"method": "totp"} |
mfa_disabled | method | {"method": "totp"} |
api_key_created | key_prefix, name | {"key_prefix": "bp_live_abc1", "name": "My API Key"} |
api_key_revoked | key_prefix, name | {"key_prefix": "bp_live_abc1", "name": "My API Key"} |
admin_login | method | {"method": "hmac"} |
account_deletion | deleted_by (self/admin) | {"deleted_by": "self"} |
Geolocalisation IP
Pour donner un contexte geographique aux evenements, l'endpoint admin GET /api/admin/audit-log enrichit chaque evenement avec la geolocalisation de l'IP. Cela permet de reperer rapidement une connexion suspecte -- par exemple, un eleveur francais qui se connecte soudainement depuis un pays inhabituel.
La geolocalisation est fournie par ip-api.com :
| Champ enrichi | Description |
|---|---|
country | Pays (code ISO + nom) |
city | Ville |
isp | Fournisseur d'acces Internet |
country_flag | URL du drapeau via flagcdn.com (PNG 16x12 + retina 32x24) |
La resolution est effectuee en batch avec un cache memoire serveur et un timeout de 3 secondes pour eviter de ralentir l'affichage.
Interface utilisateur
Filtres disponibles
L'interface de recherche est concue pour repondre a des questions specifiques : "Y a-t-il eu des echecs de connexion ce matin ?", "Quels changements de role ont ete faits cette semaine ?", "Quels sont les evenements de cet utilisateur ?"
| Filtre | Options | Description |
|---|---|---|
| Type d'evenement | Liste deroulante des 19 types | Filtre par categorie d'evenement |
| Periode | Selecteur de plage de dates (debut, fin) | Filtre temporel |
| Resultat | Succes / Echec / Tous | Filtre par resultat de l'action |
| Recherche | Champ texte libre | Recherche par nom d'utilisateur (via profiles.full_name ilike) ou adresse IP (combinaison OR) |
Cartes de statistiques
Quatre cartes synthetisent les donnees du mois en cours en haut de page, offrant un apercu immediat de l'activite et de la securite :
| Carte | Description | Calcul |
|---|---|---|
| Actions ce mois | Nombre total d'evenements enregistres | COUNT all events du mois |
| Echecs | Nombre d'evenements en echec (tentatives de connexion echouees, par exemple) | COUNT WHERE success = false |
| Utilisateurs actifs | Nombre d'utilisateurs uniques ayant genere au moins un evenement | COUNT DISTINCT user_id |
| Connexions admin | Nombre de admin_login enregistres | COUNT WHERE event_type = 'admin_login' |
Export CSV
Le bouton Exporter CSV telecharge l'ensemble des evenements correspondant aux filtres actifs (pas seulement la page courante). L'export fetche toutes les lignes filtrées (max 10 000) en une seule requete API avec skip_stats=true pour eviter le recalcul inutile des statistiques. Le fichier CSV inclut un BOM UTF-8 pour la compatibilite Excel.
Datatable
Le tableau utilise Framer Motion (pattern motion.tbody + rowVariants) avec :
- Pagination serveur (parametre
offset+limit) pour gerer de grands volumes sans surcharger le navigateur - Drapeaux de pays pour chaque IP (flagcdn.com)
- Avatar + lien cliquable vers la fiche utilisateur (
/backoffice/users/{id}) - Icones et badges colores par type d'evenement (19 mappings)
- Temps relatif (
relativeTime()centralise) avec date complete au survol
Optimisation des performances
Les cartes de statistiques (4 queries COUNT/DISTINCT sur auth_audit_log) sont recalculees uniquement lors du premier chargement ou d'un changement de filtre. Les changements de page (pagination) envoient skip_stats=true et reutilisent les stats deja en memoire, evitant 4 queries superflues a chaque clic de page. useAutoRefresh avec polling 60 secondes.
Implementation technique
Enregistrement des evenements
Les evenements arrivent dans la table auth_audit_log par trois canaux differents, selon leur origine. Cette diversite est necessaire car certains evenements sont generes cote navigateur (login), d'autres cote serveur (webhook Stripe), et d'autres par les routes admin.
| Methode | Fichier | Usage |
|---|---|---|
| Client-side | src/lib/audit-log.ts | Evenements eleveur (login, logout, MFA, password) -- POST avec Bearer token fallback |
| Server-side | src/lib/audit-log-server.ts | Evenements admin (role_change, ban, import) -- insert direct via adminSupabase |
| Direct insert | Routes API | Evenements systeme (webhook Stripe, suppression, sessions) |
L'evenement logout est enregistre via await fetch('/api/auth/audit') avec le Bearer token avant l'appel signOut(). L'usage de await est obligatoire car un fire-and-forget (envoyer la requete sans attendre la reponse) serait annule par la navigation qui suit la deconnexion -- le navigateur coupe les requetes en cours quand il change de page.
L'evenement login_success utilise le Bearer token en fallback pour l'authentification de la requete d'audit, car les cookies SSR Supabase ne sont pas encore etablis immediatement apres signInWithPassword(). Il y a un bref delai entre la connexion reussie et la disponibilite des cookies.
API Route
| Route | Methode | Auth | Description |
|---|---|---|---|
/api/auth/audit | POST | Bearer token / SSR | Enregistrer un evenement (eleveur) |
/api/auth/audit | GET | Bearer token / SSR | Historique personnel de l'utilisateur connecte |
/api/admin/audit-log | GET | Cookie HMAC admin | Journal complet avec geo IP, pagination serveur, filtres |
Retention des donnees
Les evenements sont conserves indefiniment dans la table auth_audit_log. Ce choix est delibere -- aucune politique de purge automatique n'est appliquee -- car les donnees d'audit sont essentielles pour :
- La conformite RGPD (tracabilite des acces aux donnees personnelles -- "qui a consulte les informations de cet eleveur ?")
- Les investigations de securite (analyse post-incident -- "depuis quand ce compte est-il compromis ?")
- L'historique des operations admin (changements de roles, bans, imports -- "qui a approuve ce role il y a 6 mois ?")
La colonne user_id utilise ON DELETE SET NULL : lorsqu'un utilisateur est supprime, ses logs d'audit sont conserves mais le user_id passe a NULL. Cela garantit que l'historique n'est jamais perdu meme apres une suppression de compte -- un choix essentiel pour les obligations legales et la tracabilite.
Journalisation des erreurs API
En complement du journal d'audit, le helper errorResponse() enregistre automatiquement les erreurs API (codes 4xx/5xx) dans la table api_error_logs en mode fire-and-forget (l'enregistrement n'impacte pas le temps de reponse de l'API, et si l'insertion echoue, l'erreur d'origine est quand meme retournee au client). Ces erreurs sont visibles :
- Dans le widget Error Rate du tableau de bord
- Dans la page dediee
/backoffice/error-logs
Colonne api_error_logs | Type | Description |
|---|---|---|
route | TEXT NOT NULL | Chemin API (ex : /api/queens/f0) |
method | TEXT NOT NULL | Methode HTTP (GET/POST/PUT/DELETE) |
status_code | INTEGER NOT NULL | Code HTTP (defaut 500) |
error_message | TEXT | Message tronque a 1000 caracteres |
error_stack | TEXT | Stack trace tronquee a 4000 caracteres |
metadata | JSONB | Contexte additionnel |
created_at | TIMESTAMPTZ | Horodatage |
Voir aussi : Gestion des utilisateurs | Centre de securite | Vue d'ensemble