Basic Architecture of Sport Web
This commit is contained in:
@@ -0,0 +1,35 @@
|
||||
# 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.
|
||||
Reference in New Issue
Block a user