feat(auth): agentbrain auth login/logout/status + silent refresh - #3
Merged
Conversation
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).
agentbrain login commandagentbrain auth login/logout/status + silent refresh
…h-5d5955 # Conflicts: # src/client/http-client.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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-gohas 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:POST {authUrl}/v1/auth/login/email(or/username){email|username, password}{data:{accessToken, refreshToken, expiresIn}}POST {authUrl}/v1/auth/refresh{refreshToken}POST {authUrl}/v1/auth/logoutHeader
X-Tenant-IDis required on every call.Changes
auth-command.ts—agentbrain auth {login,logout,status}.login: interactive email/username + hidden password prompt; flags--email/--username/--password/--auth-url/--tenantfor non-interactive use. Stores bothtoken(accessToken) andrefreshToken(0600). Best-effort/megreeting.logout: best-effort/v1/auth/logout+unsetConfigValues([token, refreshToken]). Network failure never blocks local clear.status:GET /mevia the refreshing client, prints identity + API/org.auth-client.ts— standalonelogin(),refresh(),logout()against Builder Auth.http-client.tssilent refresh — on 401 for bearer-auth paths, when refreshToken + authUrl + tenantId are configured, calls/v1/auth/refresh, retries the request once, and fires an injectedonTokenRefreshedcallback to persist the rotated pair. Not attempted for/mcp(X-API-Key). Failed refresh surfaces the original 401 verbatim.authUrl(default cloud auth),tenantId,refreshToken(secret, masked inconfig list). Env:AGENTBRAIN_AUTH_URL,AGENTBRAIN_TENANT_ID,AGENTBRAIN_REFRESH_TOKEN.config-managergainsunsetConfigValues()(preserves 0600).interactive-prompt.ts— reusablepromptText/promptSecret(masked, with non-TTY fallback).cli-paritytest updated (top-level group is nowauth).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/mcp401; 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.