Skip to content

feat(auth): agentbrain auth login/logout/status + silent refresh - #3

Merged
mrgoonie merged 3 commits into
mainfrom
claude/objective-booth-5d5955
Aug 6, 2026
Merged

feat(auth): agentbrain auth login/logout/status + silent refresh#3
mrgoonie merged 3 commits into
mainfrom
claude/objective-booth-5d5955

Conversation

@mrgoonie

@mrgoonie mrgoonie commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

Closes #5.

Summary

Adds full JWT lifecycle for admin/CMS commands: interactive login, silent refresh on 401, logout, and status — so users never paste a JWT again, and CMS commands stop failing mid-session when the ~7-day access token expires.

Backend finding

backend-go has no login endpoint — it delegates auth to a separate Builder Auth service (only introspects the JWT at /v1/s2s/auth/introspect). All new auth traffic targets Builder Auth directly:

Endpoint Body Result
POST {authUrl}/v1/auth/login/email (or /username) {email|username, password} {data:{accessToken, refreshToken, expiresIn}}
POST {authUrl}/v1/auth/refresh {refreshToken} new rotated pair
POST {authUrl}/v1/auth/logout Bearer accessToken revokes

Header X-Tenant-ID is required on every call.

Changes

  • auth-command.tsagentbrain auth {login,logout,status}.
    • login: interactive email/username + hidden password prompt; flags --email / --username / --password / --auth-url / --tenant for non-interactive use. Stores both token (accessToken) and refreshToken (0600). Best-effort /me greeting.
    • logout: best-effort /v1/auth/logout + unsetConfigValues([token, refreshToken]). Network failure never blocks local clear.
    • status: GET /me via the refreshing client, prints identity + API/org.
  • auth-client.ts — standalone login(), refresh(), logout() against Builder Auth.
  • http-client.ts silent refresh — on 401 for bearer-auth paths, when refreshToken + authUrl + tenantId are configured, calls /v1/auth/refresh, retries the request once, and fires an injected onTokenRefreshed callback to persist the rotated pair. Not attempted for /mcp (X-API-Key). Failed refresh surfaces the original 401 verbatim.
  • Config keysauthUrl (default cloud auth), tenantId, refreshToken (secret, masked in config list). Env: AGENTBRAIN_AUTH_URL, AGENTBRAIN_TENANT_ID, AGENTBRAIN_REFRESH_TOKEN. config-manager gains unsetConfigValues() (preserves 0600).
  • interactive-prompt.ts — reusable promptText / promptSecret (masked, with non-TTY fallback).
  • README + cli-parity test updated (top-level group is now auth).

Tests

pnpm run lint && pnpm run build && pnpm test — all green, 36 tests (up from 21).

New coverage:

  • auth-client: login (email + username), envelope + flat token, error, refresh (rotated pair, 401), logout (headers, error).
  • http-client: 401 → refresh → retry with token rotation + persistence callback; no-refresh when refreshToken missing; no-refresh on /mcp 401; original 401 surfaced when refresh itself fails.

Follow-up (per issue #5, out of scope here)

Headless/CI auth — refresh tokens rotate (unusable as static CI secret) and sk_ API keys can't reach CMS. Needs a backend decision (machine token / api-key→accessToken exchange); tracked separately.

Exchange credentials with the Builder Auth service for a bearer JWT and
persist it to config `token` (mode 0600), replacing manual
`config set token <jwt>`. Re-run when the short-lived JWT expires.

- New config keys `authUrl` (Builder Auth base URL) and `tenantId`,
  with matching env vars and the auth service default.
- `login` prompts for identifier + password interactively, hiding the
  password from the terminal; `--email`/`--username`/`--password`/
  `--auth-url`/`--tenant` allow non-interactive use.
- Standalone auth-client kept out of ApiClient since it targets a
  different host and the unversioned /v1/auth/login path.
- Best-effort /me greeting confirms the stored token works.
…fresh

Extends PR #3 to cover the rest of issue #5:

- Rename top-level `agentbrain login` → subcommand group
  `agentbrain auth {login,logout,status}`.
- Store the Builder Auth `refreshToken` alongside `token` (new secret
  config key, masked in `config list`). Env var
  `AGENTBRAIN_REFRESH_TOKEN`.
- Silent refresh in ApiClient: on 401 for a bearer-auth path with
  refresh coordinates configured, POST /v1/auth/refresh, retry the
  request once with the rotated access token, and persist the rotated
  pair via an injected `onTokenRefreshed` callback (wired in
  createClient to setConfigValue). Refresh is never attempted for /mcp
  (which uses X-API-Key), and a failed refresh surfaces the original
  401 with its actionable "run auth login" message.
- `auth logout`: best-effort server-side revoke via /v1/auth/logout,
  then unset local token + refreshToken so a transient failure never
  strands an unlogoutable session.
- `auth status`: reuse GET /me to confirm the stored token works and
  print identity + configured API/org.
- New `unsetConfigValues` helper on config-manager (0600 preserved).
- Tests: refresh + logout in auth-client, and the 401→refresh→retry
  path in ApiClient (with rejection paths for /mcp, missing
  refreshToken, and failed refresh).
@mrgoonie mrgoonie changed the title feat: add agentbrain login command feat(auth): agentbrain auth login/logout/status + silent refresh Aug 6, 2026
…h-5d5955

# Conflicts:
#	src/client/http-client.ts
@mrgoonie
mrgoonie merged commit a51ef03 into main Aug 6, 2026
@mrgoonie
mrgoonie deleted the claude/objective-booth-5d5955 branch August 6, 2026 07:52
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.

CLI: add auth login + silent token refresh (builder-auth) to remove manual JWT copy

1 participant