Guards & Decorators
Auth guards, RBAC, quota enforcement, and custom decorators
Guards
Guards run before route handlers and can reject a request with a 401/403 before any business logic executes.
ClerkAuthGuard
File: packages/server/src/common/guards/clerk-auth.guard.ts
The primary auth guard applied to almost every endpoint. It accepts two token types:
| Token Type | Format | Handling |
|---|---|---|
| Cosmo JWT | Custom 30-day JWT (issued by POST /auth/exchange-token) | Verified with AuthService.verifyCosmoToken() |
| Clerk JWT | Short-lived Clerk session token | Verified with AuthService.verifyClerkToken() |
Org resolution order:
- JWT claims (for Cosmo JWTs, org is embedded).
- Clerk JWT's
org_idclaim. X-Org-Idrequest header (used by desktop when Clerk JWT lacks org claims).
On first Clerk login, the guard syncs:
- User:
syncUserFromClerk(clerkUserId, ...) - Org:
syncOrgFromClerk(clerkOrgId, ...) - Membership:
syncMembership(clerkOrgId, clerkUserId, role)
Attaches a RequestUser to req.user:
interface RequestUser {
userId: string;
orgId: string;
orgRole: string; // "OWNER" | "ADMIN" | "MEMBER" | "VIEWER"
}RolesGuard
File: packages/server/src/common/guards/roles.guard.ts
Checks the user's orgRole against the roles specified via @Roles(). Must run after ClerkAuthGuard (role is set by the auth guard).
@UseGuards(ClerkAuthGuard, RolesGuard)
@Roles('OWNER', 'ADMIN')
@Post('org/invite')
async inviteUser() { ... }Returns 403 Forbidden if the user's role is not in the allowed list.
AdminSecretGuard
File: packages/server/src/common/guards/admin-secret.guard.ts
Protects internal /admin/* endpoints. Checks the Authorization: Bearer <token> header against process.env.CLERK_SECRET_KEY using a timing-safe comparison (crypto.timingSafeEqual) to prevent timing attacks.
Returns 401 Unauthorized if the secret does not match.
QuotaGuard
File: packages/server/src/common/guards/quota.guard.ts
Applied to the chat endpoint. Checks whether the user has exceeded their daily token quota:
- Calls
MeteringService.checkQuota(orgId, userId). - Throws
ForbiddenException→HTTP 403with{ allowed: false, usedTokens, limitTokens, percentage, plan }if quota is exceeded. - Orgs with a
tokensPerUserPerDayof-1are always allowed through.
Quota resets at midnight UTC each day.
Decorators
@CurrentUser()
File: packages/server/src/common/decorators/current-user.decorator.ts
Extracts the RequestUser from req.user (set by ClerkAuthGuard).
@Get('me')
async getMe(@CurrentUser() user: RequestUser) {
return this.authService.getUser(user.userId);
}@Roles(...roles)
File: packages/server/src/common/decorators/roles.decorator.ts
Sets the required org roles as metadata. Consumed by RolesGuard.
@Roles('OWNER', 'ADMIN')
@Delete('org/members/:userId')
async removeMember(...) { ... }@OptionalOrg()
File: packages/server/src/common/decorators/optional-org.decorator.ts
Marks an endpoint as not requiring an active org. Used on GET /auth/me so users who haven't joined an org yet can still fetch their profile.
Guard Combinations by Module
| Module / Endpoint | Guards Applied |
|---|---|
| Auth (public) | None |
| Auth (protected) | ClerkAuthGuard |
| Org management | ClerkAuthGuard + RolesGuard |
| Chat | ClerkAuthGuard + QuotaGuard |
| Workspaces | ClerkAuthGuard |
| Tasks | ClerkAuthGuard |
| Teams | ClerkAuthGuard |
| Connectors | ClerkAuthGuard |
| Billing (status/subscription) | ClerkAuthGuard |
| Billing (trial/checkout/portal) | ClerkAuthGuard + RolesGuard (OWNER/ADMIN) |
| Metering | ClerkAuthGuard |
| Updates (check) | None (public) |
| Updates (publish) | ClerkAuthGuard + RolesGuard |
| Admin | AdminSecretGuard |
| Connector Providers (list) | None (public) |
| MCP OAuth (start) | ClerkAuthGuard |
| MCP OAuth (refresh/callback/poll) | None |
| Workspace Sync | ClerkAuthGuard |