Authentication and authorization
Access JWT is global. Public routes opt out with @PublicRoute(). JwtAccessGuard also skips /metrics and /v1/metrics.
Tokens
| Token | Where |
|---|---|
| Access | Global guard. Rejected with auth.error.staleSession if tenantId no longer exists |
| Refresh | GET /v1/auth/refresh-token is @PublicRoute and uses JwtRefreshGuard with bearer scheme refreshToken |
| Impersonation | POST /v1/auth/impersonate issues a 30-minute token. Controller summary says Admin only |
Login and signup can require Cloudflare Turnstile. Blank keys disable it.
Forgot-password (POST /v1/auth/forgot-password) is public and the OpenAPI summary says it always returns success, whether or not the email is registered. The email job is password-reset-email on email_queue.
Permissions
PermissionsGuard resolves the union of platform role and tenant membership roles (PermissionResolverService, 60 second cache).
| Metadata | Result |
|---|---|
No @RequirePermissions | Guard returns true. Any authenticated user, subject to the other guards |
@RequirePermissions(...) | Every listed permission is required |
No @Roles | RolesGuard returns true |
@Roles | Platform role axis (PlatformRole) |
@TenantScoped | Rejects a JWT with no tenantId. Comment says platform admin users are exempt |
Security of a route depends on the decorator being present. There is no global deny. A new controller that omits @RequirePermissions is allowed for every logged-in user.
Permission strings look like ORDER:CREATE. The API inventory only records permissions that are written on the handler. A blank cell in that inventory means the guard allows any authenticated caller.
Warehouse confinement
WarehouseGuard resolves the warehouses the membership grants. x-warehouse-id (and route, query, or body) can only narrow that set. The guard returns true when there is no user, so public routes are not confined.
Passwords and secrets
package.json depends on both argon2 and bcryptjs. Which algorithm each flow uses was not traced.
Integration credentials use CREDENTIAL_VAULT_KEY_V1. They are not JWT signing keys.
Rate limits
Global throttler default is ttl 60 seconds and limit 100 unless config overrides. Some auth routes set a tighter @Throttle.