Cosmo Docs
Server

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 TypeFormatHandling
Cosmo JWTCustom 30-day JWT (issued by POST /auth/exchange-token)Verified with AuthService.verifyCosmoToken()
Clerk JWTShort-lived Clerk session tokenVerified with AuthService.verifyClerkToken()

Org resolution order:

  1. JWT claims (for Cosmo JWTs, org is embedded).
  2. Clerk JWT's org_id claim.
  3. X-Org-Id request 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:

  1. Calls MeteringService.checkQuota(orgId, userId).
  2. Throws ForbiddenExceptionHTTP 403 with { allowed: false, usedTokens, limitTokens, percentage, plan } if quota is exceeded.
  3. Orgs with a tokensPerUserPerDay of -1 are 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 / EndpointGuards Applied
Auth (public)None
Auth (protected)ClerkAuthGuard
Org managementClerkAuthGuard + RolesGuard
ChatClerkAuthGuard + QuotaGuard
WorkspacesClerkAuthGuard
TasksClerkAuthGuard
TeamsClerkAuthGuard
ConnectorsClerkAuthGuard
Billing (status/subscription)ClerkAuthGuard
Billing (trial/checkout/portal)ClerkAuthGuard + RolesGuard (OWNER/ADMIN)
MeteringClerkAuthGuard
Updates (check)None (public)
Updates (publish)ClerkAuthGuard + RolesGuard
AdminAdminSecretGuard
Connector Providers (list)None (public)
MCP OAuth (start)ClerkAuthGuard
MCP OAuth (refresh/callback/poll)None
Workspace SyncClerkAuthGuard