Ten named capabilities and four roles. Admin runs the platform but never sees revenue. Support reads accounts, works bugs and can enter an account to help. Sales lives in the discovery panel and the signup queue and can enter an account of someone they just sold to. The owner passes every capability by identity rather than by enumeration.
Also called: support role · sales role · admin role · least privilege · role permissions for vendor staff
- 1Routes call requirePlatformCapability('tenants.manage') rather than checking a role or an identity.
- 2A denied request returns a 403 naming the missing capability, so the failure is diagnosable rather than a bare 'Forbidden'.
- 3The owner short-circuits: hasCapability returns true for the owner without consulting any list.
- 4The full capability list is enumerated with a compiler-enforced completeness check, so adding a capability without adding it there fails the build.
- 5Capabilities are shipped to the browser so the UI can hide what it must not offer — never as the basis of a server-side decision.
- 6Access is cached briefly, with a shorter window on 'no' so a freshly accepted invitation starts working within seconds.
The header explains why the model is capability-first: 'Before this, may this person administer the platform? was answered by ~40 route handlers each holding their own copy of the same email comparison. That is exactly the shape the July SEV-1 took: an authorization rule spread thin enough that widening it in one place looked local and turned out not to be.' The enumerated owner list has its own scar: it used to be derived as the union of the other roles' lists, and when one capability was removed from admin 'the derived union dropped it and the OWNER lost the revenue UI — narrowing a subordinate role quietly narrowed the top one.' Even the failure mode is a design choice: a database error 'must never be readable as a grant.'
- The same authorization rule copied into dozens of endpoints drifts.
- Role-name checks make it impossible to move one permission without touching every route.
- A generic 'Forbidden' is indistinguishable from a bug.
See it on your own jobs
Twenty minutes, your numbers, no slide deck. We’ll build one of your real buildings in front of you and send you the estimate link at the end — yours to keep either way.
or keep browsing features →