← Back to list
01Issue
FeatureShippedSwamp CLIPublic
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
Shipped
Click a lifecycle step above to view its details.
03Sludge Pulse