RBAC — Roles & Tenancy
Three roles (
OWNER,STAFF,SUPER_ADMIN), enforced by backend middleware. Multi-tenancy is a single self-referencing FK:STAFF.ownerId → OWNER.id. SUPER_ADMIN bypasses tenancy.
1. Overview
Every authenticated request passes through authenticateToken (JWT verify) and then a role-specific guard. OWNER is the tenant root — each OWNER has exactly one Store. STAFF references an OWNER via User.ownerId and operates under that OWNER's Store. SUPER_ADMIN is the platform operator — the admin panel at /admin/* is gated by requireSuperAdmin. Frontend guards (AdminGuard.tsx) are cosmetic; real enforcement is backend-only.
2. Architecture
3. Data model
See schema.prisma:L14-L18 (UserRole enum), L31 (User.ownerId self-ref), L77 (Store.ownerId @unique — enforces 1:1).
4. Key flows
4.1 Access check
4.2 Effective owner resolution
Handlers that mutate store-scoped data call getEffectiveOwnerId(user):
OWNER→user.idSTAFF→user.ownerIdSUPER_ADMIN→ impersonation target or null
All downstream Prisma queries filter by this ownerId.
5. Permission matrix (high-level)
| Capability | OWNER | STAFF | SUPER_ADMIN |
|---|---|---|---|
| View/edit own products, orders, customers | ✓ | ✓ | ✓ (any) |
| Invite/remove team members | ✓ | ✗ | ✓ |
| Manage billing & subscription | ✓ | ✗ | ✓ |
| Store branding, catalog settings | ✓ | ✗ | ✓ |
Admin panel (/admin/*) | ✗ | ✗ | ✓ |
| Impersonate another owner | ✗ | ✗ | (deferred) |
| Deactivate a store | ✗ | ✗ | (deferred) |
6. Key files
- backend/middleware/rbac.ts —
requireRole,requireOwner,requireOwnerOrStaff,requireSuperAdmin,canAccessResource,getEffectiveOwnerId,Permissions - backend/routes/admin/index.ts:L22-L23 — mounts
authenticateToken, requireSuperAdminon every admin sub-router - schema.prisma:L14-L31 — role enum +
ownerId - src/components/admin/AdminGuard.tsx — client redirect (cosmetic only)
48 occurrences of requireRole/requireOwner*/requireSuperAdmin across 8 backend route files.
7. Env vars & config
None specific. Inherits JWT_SECRET. See authentication.md.
8. Gotchas & troubleshooting
- Impersonation is deferred → SUPER_ADMIN cannot "log in as" an OWNER yet. Planned (memory:
project_admin_pending). When building, also gate against SubscriptionGate so impersonators don't silently bypass plan limits. - Store deactivation is deferred → OWNER of a deactivated store can still hit most routes. Deferred per the same memory.
- STAFF sees ALL of the OWNER's data → No per-role granularity. STAFF reads every order, customer, product. Read-only staff = a new role.
- SUPER_ADMIN bypasses tenancy →
getEffectiveOwnerIdmay returnnull; passingnullto Prismawhere= returns everything. Handlers must guard explicitly. - Frontend guards are not security →
AdminGuard.tsxonly redirects. A stolen SUPER_ADMIN token still works viacurl. - Role change requires token refresh → Old tokens (1m–24h) carry old role claims until expiry. Force re-login when changing roles in sensitive ops.
9. Extension points
- New role → extend
UserRoleenum + migration, add guard helper, update handlers +Permissions. - Granular permissions → add
permissions: string[]onUser; check viaPermissions.X. Keep roles as coarse buckets. - Row-level ACLs → extend
canAccessResourcefor per-row checks.