Server
Auth Module
JWT, Clerk sync, org management, invitations, and webhooks
The Auth module handles all identity, token, and organisation management. Source: packages/server/src/modules/auth/.
REST Endpoints
AuthController (auth.controller.ts)
All endpoints are prefixed with /api/v1/auth.
| Method | Path | Guards | Description |
|---|---|---|---|
| GET | /cli-auth | None | CLI auth step 1 — redirect to Clerk sign-in |
| GET | /cli-callback | None | CLI auth step 2 — exchange session for Cosmo JWT |
| POST | /exchange-token | None | Desktop/web — exchange Clerk JWT for Cosmo JWT |
| POST | /onboarding-complete | ClerkAuthGuard | Mark user's onboarding complete |
| POST | /seed-onboarding | ClerkAuthGuard | Seed org with sample teams + tasks (idempotent) |
| POST | /refresh | None | Refresh expired Cosmo JWT (7-day grace) |
| GET | /me | ClerkAuthGuard + OptionalOrg | Get authenticated user profile |
| POST | /webhook | None | Clerk webhook receiver (Svix-verified) |
OrgController (org.controller.ts)
All endpoints are prefixed with /api/v1/org.
| Method | Path | Guards | Description |
|---|---|---|---|
| GET | /members | ClerkAuthGuard + RolesGuard | List org members |
| PATCH | /members/:userId/role | @Roles('OWNER') | Change a member's role |
| DELETE | /members/:userId | @Roles('OWNER', 'ADMIN') | Remove a member |
| GET | /invites | @Roles('OWNER', 'ADMIN') | List pending invitations |
| POST | /invite | @Roles('OWNER', 'ADMIN') | Invite a user by email |
| POST | /invite/:id/resend | @Roles('OWNER', 'ADMIN') | Resend an invitation |
| DELETE | /invite/:id | @Roles('OWNER', 'ADMIN') | Revoke an invitation |
Auth Service (services/auth.service.ts)
Token Operations
| Method | Description |
|---|---|
mintCosmoToken(userId, orgId, orgRole) | Issue a 30-day signed JWT |
verifyCosmoToken(token) | Verify and decode; throws on invalid/expired |
refreshToken(token) | Re-issue JWT; allows up to 7 days after expiry |
verifyClerkToken(token) | Verify Clerk JWT, return { clerkUserId, clerkOrgId, clerkOrgRole } |
User & Org Provisioning
| Method | Description |
|---|---|
syncUserFromClerk(clerkUserId, email, name, avatarUrl) | Create or update User record |
syncOrgFromClerk(clerkId, name, slug) | Create or update Organization record |
syncMembership(clerkOrgId, clerkUserId, role) | Upsert OrgMember with mapped role |
getMembership(orgId, userId) | Fetch membership or null |
markOnboardingCompleted(orgId, userId) | Set onboardingCompleted = true |
fetchClerkUser(clerkUserId) | Fetch user from Clerk Management API |
fetchFirstClerkOrg(clerkUserId) | Get user's first org from Clerk |
fetchClerkMembershipRole(orgId, userId) | Get role from Clerk (for refresh flows) |
Member Management
| Method | Description |
|---|---|
listMembers(orgId) | All members with user data |
updateMemberRole(orgId, userId, role) | Change role |
removeMember(orgId, userId) | Delete membership record |
Invitations
| Method | Description |
|---|---|
createInvite(orgId, email, role) | Generate invite with secure token (24h expiry) |
listInvites(orgId) | All pending (non-expired) invites |
resendInvite(inviteId, orgId) | Regenerate token with fresh 24h expiry |
revokeInvite(inviteId, orgId) | Delete the invite record |
Onboarding Service (services/onboarding.service.ts)
seedOrgOnboarding(orgId, userId, data?) is the entry point called by POST /auth/seed-onboarding. It:
- Creates a shared team (
"Shared") and a personal team for the user. - Creates a shared workspace (
"Shared Workspace") in the shared team. - Seeds 6 sample tasks across the personal and shared workspaces.
The seeding is idempotent — it checks for an existing shared team before creating anything. A database transaction prevents race conditions from concurrent requests.
Webhook Handler (webhooks/webhook.handler.ts)
Handles Clerk webhooks verified with Svix. Registered at POST /api/v1/auth/webhook.
| Event | Action |
|---|---|
user.created | syncUserFromClerk(...) |
user.updated | syncUserFromClerk(...) |
user.deleted | Delete User record |
organization.created | syncOrgFromClerk(...) |
organization.updated | syncOrgFromClerk(...) |
organizationMembership.created | syncMembership(...) |
organizationMembership.updated | syncMembership(...) |
organizationMembership.deleted | removeMember(...) |
organizationInvitation.created | createInvite(...) |
organizationInvitation.accepted | Mark invite accepted |
organizationInvitation.revoked | revokeInvite(...) |
Auth Repository (repositories/auth.repository.ts)
All Prisma queries for auth data are delegated here:
- User CRUD (
findByClerkId,upsert,delete) - Org CRUD (
findByClerkId,upsert) - Membership queries (
findMembership,upsertMembership,deleteMembership,listByOrg) - Invite queries (
create,findById,findByOrg,findByToken,delete)
CLI Auth Flow
- CLI calls
GET /api/v1/auth/cli-auth?port={localPort}&state={random}. - Server redirects to Clerk hosted sign-in with a callback to
GET /api/v1/auth/cli-callback. - After Clerk auth, the callback exchanges the Clerk session for a Cosmo JWT.
- Server redirects to
http://localhost:{port}/callback?token=...&state=...— the CLI's local callback server (path is/callback, not/; includes thestateparameter for CSRF protection).
Desktop Auth Flow
- Desktop opens the web app's auth page in the system browser.
- Web app authenticates via Clerk, then calls
POST /api/v1/auth/exchange-tokenwith the Clerk JWT. - Server verifies the Clerk token, syncs user/org, issues a Cosmo JWT.
- Web app deep-links back to the desktop with
cosmo://auth?token=....