Files
web_sport/docs/adr/0007-rbac-permissions-instead-of-role-checks.md
T

36 lines
1.5 KiB
Markdown

# ADR-0007: RBAC permissions instead of role checks
- **Status:** Accepted
- **Date:** 2026-08-11
## Context
`if (user.role === 'ADMIN')` spreads. Six months later authorization logic is scattered
across dozens of files, no one can answer "who can refund an order?" without grepping, and
adding a "Warehouse Supervisor" role means editing and redeploying application code.
## Decision
Authorization is expressed only as permissions (`product.update`, `order.refund`),
declared on routes with `@RequirePermissions(...)` and evaluated by a global `PermissionsGuard`.
Permissions are code: the catalog in `@sport/types` is the source of truth, and the seed
reconciles the database against it. Roles are data: rows in `roles`/`role_permissions` that a
SUPER_ADMIN edits at runtime with no deploy.
The admin sidebar is built from the same catalog, so a user never sees a link to a screen they
cannot use — presentation only; the API re-checks every request.
## Consequences
Every authorization rule is one greppable decorator. New roles need no code. The
permission set travels inside the access token, so guards do no database work on the hot path —
which is precisely why access tokens are short-lived (ADR-0008): a revoked permission takes at
most one token lifetime to take effect.
## Alternatives considered
Role checks in code — rejected above. Full ABAC/policy engine — rejected as
premature: nothing yet needs "can edit orders from their own store only". The permission model
can grow into that if a real requirement appears.