Skip to main content
← Back to list
01Issue
FeatureShippedSwamp CLI
Assigneesstack72

Relationships

↑ child of #662

#673 serve-auth: server token built-in model

Opened by stack72 · 6/17/2026· Shipped 6/18/2026

Parent

Sub-issue of #662 (serve authentication & authorization). Layer 1, item 5.

Summary

Implement the server token built-in model — the credential the server mints for authenticated users. Same shape as enrollment tokens: `.`, secret hashed at rest, timing-safe comparison. This is the server-side half of user authentication; the client-side credential storage is a sibling issue (item 6).

What to build

A `ServerToken` built-in model in `src/domain/models/access/` following the enrollment token pattern (`src/domain/models/worker/enrollment_token_model.ts`).

Schema

  • `name` — token name (the user-facing identifier, e.g. derived from the user's subject)
  • `state` — `active | expired | revoked`
  • `principalId` — the authenticated user's stable subject identifier (from OAuth `sub` claim)
  • `principalEmail` — display email (informational, not used for matching)
  • `createdAt` — timestamp
  • `expiresAt` — timestamp (~30 days default, configurable)
  • `lastUsedAt` — timestamp of last successful authentication (updated on use)
  • `vaultName` — vault reference for the hashed secret
  • `secretKey` — key under which the secret hash is stored in the vault
  • `revokedAt` — timestamp (set on revocation)

Model methods

  • `mint` — generate a secret via `generateOpaqueToken()`, store the hash in a vault, record the token lifecycle. Returns the plaintext token to the caller (this is the only time it's available — it's not stored).
  • `redeem` — validate a presented `.` using `timingSafeEqual()` against the vault-stored hash. On success, update `lastUsedAt`. Reject if expired or revoked.
  • `revoke` — transition `active → revoked`, set `revokedAt`. Idempotent. This is the primary mechanism for removing user access — must take effect immediately.
  • `expire` — transition `active → expired`. Called by a background timer or on-demand.

Key differences from enrollment tokens

  • No machine binding. Enrollment tokens bind to a machine ID on first use. Server tokens are user credentials — they work from any machine the user has the token on.
  • `lastUsedAt` tracking. Enrollment tokens don't track last use. Server tokens should, so admins can identify unused/stale tokens.
  • `principalId` field. Links the token to the authenticated user for audit and revocation purposes. When an admin revokes a user's access, they need to find tokens by principal.
  • Default expiry ~30 days (configurable). Enrollment tokens have caller-specified durations.

Reuse from enrollment tokens

  • `generateOpaqueToken()` from `src/domain/remote/session_credential.ts` — generates 256-bit opaque tokens
  • `timingSafeEqual()` from `src/domain/models/worker/enrollment_token_model.ts` — constant-time string comparison. Consider extracting this to a shared utility since both models need it.
  • Vault storage pattern — secret stored in vault, only the key reference on the model
  • Model type: `swamp/server-token`
  • Data name: `token-main` (one resource per model instance)

Scope

  • Built-in model in `src/domain/models/access/`
  • Register in the model barrel (`src/domain/models/models.ts`)
  • Unit tests covering: mint, redeem (valid + expired + revoked), revoke (idempotent), expire, lastUsedAt update
  • Ownership validation tests (same security invariant as grants — generic data writes must not modify server token records)

Out of scope

  • Client-side credential storage — sibling issue (item 6)
  • OAuth login flow — layer 4
  • Session credential issuance from server tokens — layer 4
  • Serve wiring — layer 5

References

  • Enrollment token model (primary reference): `src/domain/models/worker/enrollment_token_model.ts`
  • Opaque token generation: `src/domain/remote/session_credential.ts`
  • Grant/Group models (for access model registration pattern): `src/domain/models/access/`
02Bog Flow
OPENTRIAGEDIN PROGRESSSHIPPED+ 1 MOREASSIGNED+ 5 MOREREVIEW+ 3 MOREPR_MERGED+ 1 MORENOTIFICATION_SKIPPED

Shipped

6/18/2026, 1:49:01 AM

Click a lifecycle step above to view its details.

03Sludge Pulse
stack72 assigned stack726/18/2026, 12:19:15 AM
stack72 linked parent of #6626/19/2026, 3:06:30 PM

Sign in to post a ripple.