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

1.5 KiB

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.