Authorization¶
Statement¶
Every authenticated request to ClaimGuard passes through a two-axis
authorization check: a role test (the caller's app_role allows
the action) and an org-scope test (the row being read or written
belongs to the caller's org_ref, or the caller is a super-admin).
Both checks are enforced at the application layer in middleware and
in route handlers; there is no client-side role gate that, if bypassed,
exposes data.
The model is small on purpose — four roles and one tenant attribute — so the surface area auditors review is small, and so a reviewer reading a route handler can verify the authorization story by inspection.
Implementation¶
Role model¶
Four built-in roles are defined and enforced in
server/src/middleware/auth.js:
| Role | Scope | Notes |
|---|---|---|
SUPER_ADMIN |
Cross-org | Bootstrap administrators. Can create other super-admins. Can administer any org's resources. |
ADMIN |
Single org | Org-scoped administration: user management, configuration, claim review. |
REVIEWER |
Single org | Default analyst role. Reviews claims, writes notes. |
EXEC_VIEWER |
Single org | Read-only dashboard access. |
Role grants happen via user creation (POST /api/users) and update
(PUT /api/users/:id). Only super-admins can grant SUPER_ADMIN.
Admins can grant non-super-admin roles within their own org.
Role enforcement¶
Three middlewares are exported from server/src/middleware/auth.js:
requireAuth— verifies a valid JWT is present, then re-fetches the user row from the DB and populatesreq.userwith the currentid,email,app_role,org_ref,display_name, andfull_name. Returns 401 on missing/invalid token, expired token, or a user whoseis_activeis false. API keys are a separate authentication path handled byserver/src/middleware/apiKeyAuth.js, not byrequireAuth.requireAdmin— callsrequireAuthfirst, then checksreq.user.app_roleis one of['ADMIN', 'SUPER_ADMIN']. Returns 403 otherwise.requireSuperAdmin— callsrequireAuthfirst, then checksreq.user.app_role === 'SUPER_ADMIN'. Returns 403 otherwise.
A route's authorization is therefore visible at the route declaration:
// Reviewer-or-up:
router.get('/api/claims/:id', requireAuth, ...)
// Admin-or-up:
router.put('/api/users/:id', requireAdmin, ...)
// Super-admin only:
router.delete('/api/orgs/:id', requireSuperAdmin, ...)
Org-scope enforcement¶
Org scoping is enforced at the SQL layer of every route that touches
tenant data. The pattern (visible in server/src/routes/_crud.js and
in route-specific handlers) is:
- Every tenant-bound row carries an
org_refforeign-key column. - Every list query filters on
WHERE org_ref = $org_ref. - Every detail / update / delete query joins or filters on the same.
- Super-admins bypass the filter for cross-org administration.
A representative example from server/src/routes/users.js lines
108-112:
// Non-super-admins can only view users in their organization
if (req.user.app_role !== 'SUPER_ADMIN' &&
user.org_ref && user.org_ref !== req.user.org_ref) {
return res.status(403).json({ error: 'Access denied' });
}
This pattern is the application's IDOR (Insecure Direct Object Reference) defense: even if a caller guesses or enumerates valid resource IDs from another org, the org-scope check returns 403.
API-key authorization¶
API keys carry their own authorization data:
org_ref— the key's tenant. Every query made under the key is scoped the same way as a human request from that org.scopes— a per-key array consulted inserver/src/middleware/apiKeyAuth.js. Routes that require API-key auth check the scope set before serving.- Issuer constraint — admins can only create keys for their own org; super-admins can create keys for any org.
See Authentication for the full API-key model.
What runs in the middleware chain¶
For a typical authenticated route, the request passes through:
- CORS / Helmet / standard middleware (
server/src/index.js). - IP-based rate limiter (per
docs/security/README.md—server/src/middleware/rateLimit.js). requireAuth(orrequireAdmin/requireSuperAdmin) — identity + role.- Per-user post-auth rate limiter for admin-class endpoints.
- Zod validator for request body / params (
validatemiddleware). - Route handler — runs the org-scoped query.
Cross-cutting invariants¶
docs/security/README.md lists the authorization-relevant invariants
this codebase depends on:
- The audit-log endpoint is org-scoped (super-admins see cross-org; others see only their own org).
- The rate limiter is mounted after auth for admin endpoints, so it can scope by user, not just by IP.
- Routes that operate on a resource by ID always verify ownership before action, never trusting that the listing routes already filtered.
A PR that weakens any of these invariants must either add an
equivalent invariant or document the regression with a .snyk-style
written reason — see Change management.
Status¶
implemented — verified 2026-04-30.
What's in place:
- Four-role model with documented capability boundaries.
- Three middleware wrappers (
requireAuth,requireAdmin,requireSuperAdmin) used at every protected route. - Per-request DB re-fetch in
requireAuth—app_roleandorg_refare read from the liveusersrow on every request, so role downgrades and org transfers take effect on the user's next call without a re-login. - Org-scoping at the SQL layer with super-admin override.
- IDOR defense via consistent org-scope checks in detail / update / delete routes.
- API-key scopes and per-key org binding.
Known gaps¶
- No fine-grained permissions. Roles are coarse; a customer that wants "REVIEWER without claim-delete" or "REVIEWER with read-only exports" must wait for a more granular permission model.
- No external attribute-based access control (ABAC). All authorization decisions are encoded in code, not in an external policy engine (e.g., OPA). Fine for current scale; revisit if a customer demands externalized policy.
- No documented periodic access review. Quarterly review of human-user roles and API-key inventory is a roadmap item.
Roadmap¶
- Quarterly access review of users + API keys, with sign-off recorded — before SOC 2 fieldwork.
- Fine-grained permissions ("entitlements") if a customer requirement demands them.
- Externalized policy engine (OPA / Cedar) — not before there's a customer need.