Skip to content

Authorization (permissions)

Permissions in the <MODULE>.<VERB> format. From the latest live audit (GET /admin/permissions, full catalog — 2026-09-27): 18 modules, 94 codes. That count has already changed several times since the project started (12→14→15→17→18 modules) and will keep changing — never treat it as fixed. Current modules: RESIDENCE PRESSING RESTAURANT SALLE_FETE FACTURATION FINANCES RH RESIDENT CLIENT MARCHANDISE ADMIN AUDIT CORE SIGNALEMENT DEPENSE PORTAIL ABONNEMENT RAPPORTS.

Verbs are no longer just the 4 generic ones (VOIR/CREER/ MODIFIER/SUPPRIMER): several modules have their own verbs, added as backend features landed — ENCAISSER, GERER_TARIFS, COMMANDER, DECLARER, DEMANDER, VALIDER, ANNULER, GERER_CATALOGUE, VENDRE, AJUSTER, DECIDER_RELIQUAT, DECLARER_TIERS, plus at least SUPERVISER/ RETIRER/TRAITER/MARQUER_PRET.

The frontend hardcodes them in src/core/permissions/types.ts (PermissionCode = union of combinations), documented module by module with the audit date and verification method. This union is never exhaustive by design: a missing verb/module does not mean it does not exist on the backend, only that it has not been met and verified yet. Before assuming a module/verb is absent, check live:

Fenêtre de terminal
# Full catalog (every code that exists)
curl -s https://dev.sim.strife-cyber.org/api/v1/admin/permissions \
-H "Authorization: Bearer $TOKEN"
# Permissions of a given role
curl -s https://dev.sim.strife-cyber.org/api/v1/admin/roles/$ID/permissions \
-H "Authorization: Bearer $TOKEN"

⚠️ Lived case, worth remembering: the RAPPORTS module is real and distinct from ADMIN, but the whole frontend (5 routes, sidebar, home, Ctrl-K, global dashboard) gated it on ADMIN.VOIR for weeks on the word of a comment that was never re-verified. Real consequence: the 5 Manager roles, which have RAPPORTS.VOIR without ADMIN.VOIR, were locked out of their own reports. Lesson: a comment claiming a module/permission “doesn’t exist on the backend” silently goes stale.

The logged-in user receives permissions via GET /auth/me (permissions: string[]). They are stored in the session snapshot (useAuth().user.permissions).

// hooks (core/auth/hooks.ts)
const user = useCurrentUser() // AuthMeResponse | null
const permissions = usePermissions() // string[]
const canVoir = useCan('FINANCES.VOIR')
// route guards (core/auth/guards.ts)
beforeLoad: ({ context }) => {
requirePermissions(context.auth, 'FINANCES.VOIR', 'FINANCES.MODIFIER')
}
// pure helpers (core/permissions)
hasPermission(permissions, 'RH.CREER')
hasAnyPermission(permissions, 'RH.CREER', 'RH.MODIFIER')
hasAllPermissions(permissions, 'RH.VOIR', 'RH.MODIFIER')

The frontend is not a security boundary. Client-side permissions are only a UI convenience (show/hide, pre-redirect). The backend actually enforces permissions on every endpoint. Never trust client permissions to protect data.

  • ❌ Invent a prefix (SALLE_FETE and SALLEFETE are different) or a verb (DELETE, EXPORTER…) that doesn’t exist on the backend.
  • ❌ Hardcode a page without a gate, assuming only the backend protects — the UI must reflect the model (hide what’s forbidden).
  • ❌ Hide sensitive data relying solely on useCan(): the network response is the only source of truth.