Authorization (permissions)
The real backend model
Section titled “The real backend model”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:
# 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 rolecurl -s https://dev.sim.strife-cyber.org/api/v1/admin/roles/$ID/permissions \ -H "Authorization: Bearer $TOKEN"⚠️ Lived case, worth remembering: the
RAPPORTSmodule is real and distinct fromADMIN, but the whole frontend (5 routes, sidebar, home, Ctrl-K, global dashboard) gated it onADMIN.VOIRfor weeks on the word of a comment that was never re-verified. Real consequence: the 5 Manager roles, which haveRAPPORTS.VOIRwithoutADMIN.VOIR, were locked out of their own reports. Lesson: a comment claiming a module/permission “doesn’t exist on the backend” silently goes stale.
Where permissions come from
Section titled “Where permissions come from”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 | nullconst 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')Security rule
Section titled “Security rule”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.
Don’ts
Section titled “Don’ts”- ❌ Invent a prefix (
SALLE_FETEandSALLEFETEare 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.
