Skip to content

feat(auth): unify tokens into v2 user-owned store (v1 SAS becomes read-only)#74

Merged
steve-krisjanovs merged 1 commit into
mainfrom
feat/v2-tokens-unify
Jun 26, 2026
Merged

feat(auth): unify tokens into v2 user-owned store (v1 SAS becomes read-only)#74
steve-krisjanovs merged 1 commit into
mainfrom
feat/v2-tokens-unify

Conversation

@steve-krisjanovs

Copy link
Copy Markdown
Contributor

Replaces #73, which was auto-closed when its base branch (feat/yank-orch) was deleted on #72 merge. Same content, rebased onto main.

Summary

Collapses llmux's two parallel auth systems into one. Tokens minted via the web UI's Tokens page or llmux token create now all flow through the v2 FileTokenStore and carry an owning username. The v1 SAS store becomes read-only: existing tokens keep validating, no new ones can be minted from any code path.

Build delta: dist/index.js 375 KB → 382 KB (+7 KB).

Surface changes

  • API /api/tokens — all handlers routed through v2 FileTokenStore via new getV2Stores() export from v2-routes.ts. POST accepts username (default = v2-authed caller; admin can override; non-admin payload server-rewritten to self). GET returns { me, tokens, users? } (was a bare array). Ownership enforced on PATCH/DELETE.
  • Web UI — Owner column in the Tokens table; Owner picker on the create form (admin sees all users, non-admin locked to self).
  • CLIllmux token create --username <name> is required. New --user <name> filter on list/revoke. New --qr-owner <name> flag on server start.
  • Boot QR — mints v2 token for the first admin user; ⏭ Skipping pairing QR if no admin yet (defers to /setup wizard URL).
  • Auth check unificationisAuthorized and isWsAuthorized accept v1 SAS OR v2 session (both became async).

v1 sunset

  • authStore.createAuthToken / renameAuthToken / revokeAuthToken / revokeAllAuthTokens — zero call sites post-PR
  • authStore.validateAuthToken / listAuthTokens / authEnabled — kept for read-only legacy validation
  • Existing v1 SAS tokens continue to validate; no new ones can be minted
  • A future release will delete the v1 store entirely

Backwards compatibility

  • Existing sas_… tokens keep working — no rotation forced on phones / CI / scripts.
  • llmux token create without --username now errors.
  • /api/tokens GET response shape changed from bare array to { me, tokens, users? }.
  • Tokens page requires v2 login.

Smoke test (verified on cachy)

  • ✅ Build clean, typecheck clean
  • ✅ Daemon boots: [v2] auth ready (2 users); /login, /account, /admin/users available
  • ✅ Boot QR auto-picks first admin: ✓ created pairing token (..., owner: stevekrisjanovs)
  • /api/tokens → 401 without auth, {ok:false, error:"authentication required"} with empty cookie
  • ✅ Web UI Owner column + picker visible on phone via Tailscale HTTPS

🤖 Generated with Claude Code

…d-only)

Collapses the two parallel auth systems into one. Tokens now have an
owning username; the v1 SAS store is read-only (existing tokens keep
validating, no new ones can be minted).

Why: the Tokens page minted anonymous v1 SAS tokens while the Users
page (added v0.35.0) managed v2 identity-bound tokens — two doors
with mismatched audit characteristics. With orch yanked, v2 user
accounts had no live consumer except browser login itself. Time to
make them the canonical auth source.

API (server.ts):
- /api/tokens POST routes through v2 FileTokenStore.mint(), accepts
  optional `username` (defaults to v2-authed caller; admin can mint
  for any user; non-admin payload server-rewritten to self)
- /api/tokens GET returns {me, tokens, users?} — admins get the user
  list for picker population; non-admin sees only own tokens
- /api/tokens DELETE/PATCH enforce ownership (non-admin: 403 on
  other users' tokens)
- isAuthorized + isWsAuthorized now accept v1 SAS OR v2 session
  (was v1-only); becomes async to do the v2 check

Web UI (pickerPage):
- Owner column in the table
- Owner picker on the create form (admins see all users; non-admin
  locked to self via `disabled`)
- Auth gate: tokens page requires v2 login

CLI (handlers.ts + index.ts):
- `llmux token create --username <name>` is REQUIRED; no default
- `llmux token list` includes USER column; new `--user <name>` filter
- `llmux token revoke --all [--user <name>]` filters by owner
- `llmux token rename` uses v2 store
- New `--qr-owner <name>` flag on `server start`

Boot QR (handlers.ts:printServerStartQr):
- Mints v2 token for first admin user (or --qr-owner override)
- If no admin exists yet, skips QR + defers to /setup wizard URL

v2 token store (v2/auth/tokens.ts):
- Added `rename(tokenId, name)` to TokenStore interface +
  FileTokenStore impl (was previously only on v1 store)
- Exported getV2Stores() from v2-routes.ts so /api/tokens can mint
  through the same singleton stores used by /login

v1 sunset:
- authStore.createAuthToken / renameAuthToken / revokeAuthToken /
  revokeAllAuthTokens have zero call sites in the codebase now
- authStore.validateAuthToken + listAuthTokens + authEnabled stay
  for read-only legacy validation
- Next release will likely delete the v1 store entirely

Build: dist/index.js 375 KB → 382 KB (+7 KB).

Stacks on feat/yank-orch (#72). Smoke test: daemon boots, mints v2
pairing token for first admin user, /api/tokens returns 401 without
auth, returns {me, tokens, users} with v2 session.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@steve-krisjanovs steve-krisjanovs merged commit 9bf3e8b into main Jun 26, 2026
1 check passed
@steve-krisjanovs steve-krisjanovs deleted the feat/v2-tokens-unify branch June 26, 2026 15:22
steve-krisjanovs added a commit that referenced this pull request Jun 26, 2026
…2 user-owned

Bundles two significant changes from #72 + #74:

- Orch bus + Channels nav surface removed (~2,000 lines deleted)
- Tokens unified into v2 user-owned store; v1 SAS becomes read-only

See CHANGELOG.md [0.37.0] entry for the full surface inventory and
operator impact.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant