Skip to content

Auth model

Cairn authenticates callers with an opaque session cookie and resolves every request into a principal — a user, their active workspace, and their role in that workspace — before your handler runs.

When a sign-in flow succeeds, Cairn mints a session and sets an opaque cairn_session cookie. The cookie holds a 32-byte random token; only its SHA-256 hash is stored in the sessions table, so a database leak does not expose usable tokens. The cookie is set HttpOnly, Secure, and SameSite=Lax.

On each request, the auth interceptor resolves the cookie into the caller’s principal — their user id, active workspace, and role — and makes it available to your handler. Two RPCs work directly with the session:

  • WhoAmI reports the resolved identity for the current session.
  • Logout revokes the session server-side and clears the cookie.

Cairn is multi-tenant by way of workspaces. cairn.v1.WorkspaceService lets an authenticated caller manage them:

  • Create a workspace — the creator becomes its owner.
  • List workspaces and roles — every workspace the caller belongs to.
  • Switch the active workspaceSetActiveWorkspace changes which workspace subsequent requests are scoped to.
  • Invite an existing user by email into a workspace.
  • Change a member’s role.

Roles are hierarchical: owneradminmember. Cairn resolves the caller’s role in the active workspace into the request context, so you gate a handler by requiring a minimum role:

if err := cairn.RequireRole(ctx, "admin"); err != nil {
return nil, err // caller is below admin in the active workspace
}

Because the hierarchy is inclusive, RequireRole(ctx, "admin") is satisfied by both admins and owners.

An admin can revoke a member’s access to the active workspace without removing them. DisableMember(user_id) marks the membership disabled; EnableMember(user_id) restores it. A disabled member keeps their membership row and role — ListMembers still returns them, flagged disabled: true — but the change takes effect at resolution time: on every request, a disabled membership resolves to no active workspace and no role.

That resolution-time enforcement is what makes disabling cheap for your application. The member stays authenticated — WhoAmI still shows them signed in — but cairn.ActiveWorkspace(ctx) returns false, so every workspace-scoped surface (Cairn’s own RPCs, file transfer, and any handler you gate on the active workspace) denies the request automatically. You write no extra code.

Re-enabling is instant and reversible. EnableMember clears the flag, and the member’s same existing sessions and API keys regain access on their next request — no re-login and no re-issued credentials. Both actions are audited fail-closed (member.disabled / member.enabled).

  • Sign-in — the flows that mint sessions: magic link, OAuth, and passkeys.
  • MFA — adding a second factor on top of the session.
  • Data model — the tables behind users, sessions, workspaces, and memberships.