Authorization Stack
Layer 1: Session Validation (SessionGuard)
Validates that the request carries a valid session cookie. No route-level override; this runs on every request.- File:
backend/src/auth/session.guard.ts - Behavior: Calls
authService.getSession(headers). Returns 401 if no session. - Scope: Global, applied to all routes
Layer 2: Capability-based permissions (PermissionsGuard)
Routes declare the permissions they require. The guard resolves the user’s roles and checks whether they carry every required permission.- File:
backend/src/auth/permissions.guard.ts - Behavior: Reads
@Permissions(...)or@Roles(...)metadata. QueriesUserRoletable. Checks AND semantics for@Permissions, OR semantics for@Roles. - Scope: Global, but only enforced when the route has
@Permissions()or@Roles()metadata. Routes without these decorators pass through.
@Permissions vs @Roles
@Permissions(...) is the preferred approach. @Roles(...) is retained for backwards compatibility only.
What PermissionsGuard attaches to the request
After successful authorization, the guard attaches three properties:Layer 3: Store-Level Scope Enforcement (ScopeGuard)
Enforces that a scoped user (Store Manager, HR) can only access data within their assigned boundary.- File:
backend/src/auth/scope.guard.ts - Behavior: Reads
@RequireScope({ storeIdParam: 'storeId' })metadata. For STORE_MANAGER, verifies thestoreIdin the URL params matches their assigned store. For HR, verifies the store belongs to their client. Admins bypass all scope checks. - Scope: Global, but only enforced when the route has
@RequireScope()metadata
Scope Rules
How roles map to permissions
The mapping is defined inbackend/src/auth/permissions.ts as four composable groups (base, scoped manager, regional, HR) plus ALL_PERMISSIONS for admin roles. The full group table and role-permission matrix are in Roles and permissions.