Administration

Référence technique du domaine Administration — console super-admin, paramètres applicatifs, gestion des organisations, webhooks sortants, audit log et supervision.

Administration

Périmètre : console super-admin Pulse (gestion multi-tenant des organisations clientes), paramètres applicatifs globaux et par organisation, release notes, webhooks sortants, impersonation, audit log cross-org et supervision temps réel. Ce domaine est strictement réservé à l'équipe Pulse Group ; aucun utilisateur d'organisation cliente n'y a accès.

La console super-admin (/admin/*) affiche un banner rouge persistant et exige un MFA récent (< 24 h) pour toute action destructive. Routes API sous /api/admin/*. Source de cette page : docs/16-domain-admin.md.
État d'implémentation — seul un sous-ensemble du périmètre cible est livré. Routes /api/admin/* réellement implémentées : GET stats, GET audit, GET/POST organizations, GET organizations/[id], GET organizations/[id]/roles, GET roles, GET users, POST invitations. Tout le reste décrit ci-dessous (suspension/archivage/export d'org, impersonation, webhooks, release notes, banner, paramètres applicatifs, supervision, et l'espace /workspace/admin/*) relève de la cible non encore implémentée et est signalé en tant que tel section par section.

Vocabulaire & entités

TermeEntitéDéfinitionStatut
Super-admin PulseMembre de l'équipe Pulse Group exploitant la plateforme.
OrganisationOrganizationTenant (client, bailleur ou partenaire) ; isolation complète des données.implémenté
Organization SettingOrganizationSettingParamètre propre à un tenant (marge par défaut, numérotation custom…).modèle présent
Application SettingApplicationSettingParamètre global clé/valeur (chiffré si préfixe secret:).cible (modèle absent)
Release NoteReleaseNoteNotice de version exposée aux utilisateurs à la prochaine connexion.cible (modèle absent)
WebhookWebhookURL HTTPS externe déclenchée sur événement métier.cible (modèle absent)
Webhook DeliveryWebhookDeliveryTentative d'envoi d'un webhook (statut, retry, payload).cible (modèle absent)
Cible — non encore implémenté. Dans le schéma Prisma actuel, seules Organization et OrganizationSetting existent. ApplicationSetting, ReleaseNote, Webhook et WebhookDelivery ne sont pas encore des modèles.

La structure complète est dans docs/04-data-model.md §14.

Écrans

Console super-admin (/admin/*)

ÉcranRouteContenuStatut
Dashboard/adminStats globales (orgs actives, MAU, volume API, DB, stockage), courbe MAU 12 mois, alertes système.partiel (GET /api/admin/stats)
Organisations/admin/organizationsDataTable : nom, slug, SIREN, plan, statut, membres, assets, contrats actifs.implémenté (liste + création)
Fiche organisation/admin/organizations/[id]Onglets : Synthèse, Membres, Paramètres, Quotas, Webhooks, Audit, Actions.partiel (synthèse + rôles ; onglets Paramètres/Quotas/Webhooks/Actions = cible)
Utilisateurs globaux/admin/usersTous les users cross-org. (Suspension, reset password, impersonation = cible.)partiel (liste seule)
Paramètres applicatifs/admin/settingsÉdition des ApplicationSetting par section.cible non implémenté
Release Notes/admin/release-notesCRUD + publication, audience, KPI d'accusé de lecture.cible non implémenté
Webhooks/admin/webhooksListe + création + historique des livraisons, action Resend.cible non implémenté
Audit log global/admin/auditCross-org, filtres acteur/action/target/période.implémenté (GET /api/admin/audit)
Supervision/admin/supervisionQueues, sessions DB, latence HTTP P95, lien Grafana/Loki.cible non implémenté
Banner global— (API)Message affiché sur toutes les pages, audience configurable.cible non implémenté
Cible — non encore implémenté : ApplicationSetting / paramètres applicatifs, release notes, webhooks sortants, supervision et banner global ne disposent d'aucune route /api/admin/* à ce jour. Les écrans listés ci-dessus n'existent que pour les lignes marquées « implémenté » ou « partiel ».

Paramètres workspace (/workspace/admin/settings/*)

Cible — non encore implémenté. Aucune route /api/workspace/admin/* n'existe dans le code. La gestion par l'admin d'organisation (utilisateurs, rôles, API keys, paramètres) passe aujourd'hui par les routes /api/workspace/members, /api/workspace/roles, /api/workspace/api-keys et /api/workspace/invitations (cf. domaine Identity). Le tableau ci-dessous décrit la cible.
OngletContenu
GénéralOrganizationSetting éditables par l'admin de l'org.
IntégrationsWebhooks sortants de l'org, lien vers /admin/webhooks.

Modèle de données (points clés)

Cible — non encore implémenté. Les points ci-dessous décrivent le modèle cible. Les modèles ApplicationSetting, ReleaseNote, ReleaseNoteAcknowledgement, Webhook et WebhookDelivery ne sont pas encore au schéma, et Organization ne porte pas de cycle SUSPENDED/ARCHIVED à ce jour.
  • ApplicationSetting.value est JSON ; les secrets sont chiffrés au repos (préfixe clé secret:).
  • ReleaseNoteReleaseNoteAcknowledgement (table de liaison user/release) : la modale "Nouveautés" est masquée dès qu'un ack existe.
  • Webhook.secret est rotatable avec une période de grâce de 7 jours (dual-sign : ancien et nouveau secret acceptés pendant la transition).
  • WebhookDelivery.status : PENDINGDELIVERED | FAILED (retry exponentiel, 10 tentatives max).
  • Organization.status : ACTIVESUSPENDEDARCHIVED (irréversible après la période de grâce de 90 j).

API

Les routes /api/admin/* sont déclarées avec requireOrganization: false et une permission dédiée portée par le rôle SUPERADMIN_PROVIDER dans le seed.

Routes implémentées

Permissions vérifiées directement dans chaque handler.

MéthodeRoutePermission
GET/api/admin/statsadmin.org:read
GET/api/admin/auditadmin.audit:read
GET/api/admin/organizationsadmin.org:read
POST/api/admin/organizationsadmin.org:create
GET/api/admin/organizations/[id]admin.org:read
GET/api/admin/organizations/[id]/rolesadmin.org:read
GET/api/admin/rolesidentity.role:manage
GET/api/admin/usersidentity.user:read
POST/api/admin/invitationsadmin.org:read
POST /api/admin/invitations crée une invitation depuis la console super-admin (corps : organizationId, email, role). C'est aujourd'hui la seule mutation admin au-delà de la création d'organisation.

Routes cible (non implémentées)

Cible — non encore implémenté. Aucune des routes suivantes n'existe dans le code. Le tableau décrit l'API cible décrite par docs/16-domain-admin.md.
MéthodeRoutePermission cible
GET/api/admin/dashboardadmin.org:read
PATCH/api/admin/organizations/[id]admin.org:update
POST/api/admin/organizations/[id]/suspendadmin.org:suspend
POST/api/admin/organizations/[id]/archiveadmin.org:suspend
POST/api/admin/organizations/[id]/exportadmin.org:read
POST/api/admin/users/[id]/impersonateidentity.user:impersonate
GET / PATCH/api/admin/settingsadmin.settings:read / :update
GET / POST/api/admin/release-notesadmin.release_notes:manage
POST/api/admin/release-notes/[id]/publishadmin.release_notes:manage
POST/api/account/release-notes/[id]/acknowledge(authentifié)
GET / POST / PATCH / DELETE/api/admin/webhooks[/...]admin.webhooks:manage
GET/api/admin/supervisionadmin.audit:read
GET / POST/api/admin/banneradmin.settings:update (POST)
POST/api/admin/organizations/[id]/suspendAuth

Cible — non encore implémenté. Suspendrait une organisation : invalide toutes les sessions actives, refuse les connexions ultérieures et conserve les données en lecture seule.

Corps (JSON)

reason
string required
Motif de suspension (affiché dans l'audit log et notifié à l'admin de l'org).

Requête

curl -s -X POST "$API/api/admin/organizations/$ORG_ID/suspend" \
  -H "Content-Type: application/json" -H "Cookie: $SESSION" \
  -d '{"reason":"Impayés — suspension préventive"}'

Réponse

{ "id": "org_…", "status": "SUSPENDED", "suspendedAt": "2026-06-16T09:00:00Z" }

Workflows

Cible — non encore implémenté. Les workflows ci-dessous (suspension/archivage d'org, webhooks sortants, release notes, backup & restauration) décrivent le comportement cible ; aucun n'a de route ni de job correspondant dans le code actuel.

Suspension d'organisation

ACTIVE
  └──→ SUSPENDED  (raison obligatoire + double confirmation)
         ├─ sessions membres invalidées
         ├─ login refusé (page dédiée)
         ├─ API rejette toutes requêtes sauf endpoints admin
         ├─ audit log + notification email admin org + super-admin
         └──→ ARCHIVED après 90 j de grâce (irréversible)
                ├─ tables purgées (sauf audit, factures, contrats — obligation légale)
                └─ zip JSON archivé S3 (10 ans)

Webhooks sortants

Événement métier (ex : lease.contract.signed)
  └──→ webhooks.service.dispatch(event, payload)
         └──→ pour chaque Webhook souscrit
               ├─ crée WebhookDelivery (PENDING)
               └─ job send-webhook-deliveries (toutes les 30 s)
                    ├─ charge 50 deliveries PENDING
                    ├─ signe body HMAC
                    ├─ POST → si 2xx → DELIVERED
                    └─ sinon retry exponentiel (max 10) → FAILED + alerte

Format payload standard :

{
  "event": "lease.contract.signed",
  "deliveredAt": "2026-06-16T10:30:00Z",
  "data": { "...": "..." },
  "organizationId": "org_…",
  "webhookId": "wh_…"
}

Headers envoyés :

X-Pulse-Event: lease.contract.signed
X-Pulse-Delivery: <delivery-id>
X-Pulse-Signature: t=<timestamp>,v1=<hex-hmac>

Release Notes

Création (draft) → Publication → Modal "Nouveautés" à la prochaine connexion
  └──→ bouton "OK, lu" → ReleaseNoteAcknowledgement
         └──→ KPI : % users ayant accusé réception

Backup & restauration

  • Snapshot DB horaire (rétention 7 j), quotidien (rétention 30 j), PITR 7 j.
  • Versioning S3 activé ; Object Lock sur buckets légaux.
  • DR drill trimestriel documenté.

Règles métier

Séparation console vs workspace

/admin/* = exclusif SUPERADMIN_PROVIDER (routes /api/admin/* avec requireOrganization: false). Les admins d'organisation gèrent leur tenant via les routes /api/workspace/* (members, roles, api-keys, invitations). L'espace /workspace/admin/* cible n'existe pas encore. Aucune route /api/admin/* n'est accessible depuis un contexte workspace.

Verrous de sécurité

  • Création d'org réservée à SUPERADMIN_PROVIDER (admin.org:create). Implémenté.
  • Le plugin admin Better Auth est branché (admin({ impersonationSessionDuration: 3600 })) mais adminRoles n'est pas configuré : l'autorisation passe par le RBAC maison Pulse, pas par les rôles globaux Better Auth.
  • Cible : au moins 1 SUPERADMIN_PROVIDER actif en permanence, impersonation auditée en lecture seule, MFA récent (< 24 h) pour les actions destructives.

Webhooks (cible)

Cible — non encore implémenté.

  • URL de destination en HTTPS uniquement.
  • Secret rotatable : dual-sign pendant 7 j (ancien + nouveau secret acceptés).
  • ApplicationSetting contenant un secret → préfixe secret: → chiffrement au repos automatique.

Cycle de vie organisation (cible)

Cible — non encore implémenté.

  • Suspension réversible pendant 90 j.
  • Archivage = définitif (données légales conservées : audit, factures, contrats).
  • Export données → ZIP via URL signée temporaire.