Skip to main content

Authentication and authorization

Access JWT is global. Public routes opt out with @PublicRoute(). JwtAccessGuard also skips /metrics and /v1/metrics.

Tokens​

TokenWhere
AccessGlobal guard. Rejected with auth.error.staleSession if tenantId no longer exists
RefreshGET /v1/auth/refresh-token is @PublicRoute and uses JwtRefreshGuard with bearer scheme refreshToken
ImpersonationPOST /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).

MetadataResult
No @RequirePermissionsGuard returns true. Any authenticated user, subject to the other guards
@RequirePermissions(...)Every listed permission is required
No @RolesRolesGuard returns true
@RolesPlatform role axis (PlatformRole)
@TenantScopedRejects a JWT with no tenantId. Comment says platform admin users are exempt
Known risk

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.