Route auth status/list/revoke/logout through TokenForResource#1258
Route auth status/list/revoke/logout through TokenForResource#1258khaong wants to merge 1 commit into
Conversation
There was a problem hiding this comment.
Pull request overview
This PR fixes split-host (ENTIRE_AUTH_BASE_URL ≠ ENTIRE_API_BASE_URL) auth-token management by ensuring auth status/list/revoke/logout obtain a data-API-audience bearer via auth.TokenForResource (RFC 8693 exchange) rather than sending the raw keyring token to the data API. It also improves error propagation by preserving the auth.ErrNotLoggedIn sentinel across NewAuthenticatedAPIClient, and refines activity to only show the “Not logged in” hint when that sentinel is actually present.
Changes:
- Route auth token list/revoke/current-revoke through a centralized data-API token resolver (
resolveDataAPIToken) and remove caller-supplied bearer parameters to prevent audience-mismatch regressions. - Preserve
auth.ErrNotLoggedInacrossNewAuthenticatedAPIClientand updateactivityto gate the friendly login hint onerrors.Is(..., auth.ErrNotLoggedIn). - Update auth/logout unit tests to match the new function signatures (implementations now resolve their own bearer).
Reviewed changes
Copilot reviewed 6 out of 6 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| cmd/entire/cli/auth.go | Adds resolveDataAPIToken, updates list/revoke flows to resolve their own data-API-scoped bearer, and relaxes tokenmanager HTTP guard when --insecure-http-auth is set. |
| cmd/entire/cli/logout.go | Updates server-side revoke to internally resolve a data-API token and removes the caller-supplied bearer parameter. |
| cmd/entire/cli/api_client.go | Wraps “not logged in” errors to preserve the auth.ErrNotLoggedIn sentinel for errors.Is checks. |
| cmd/entire/cli/activity_cmd.go | Only prints the “Not logged in…” hint when the error is actually auth.ErrNotLoggedIn; otherwise returns the real error. |
| cmd/entire/cli/auth_test.go | Updates injected lister/revoker function signatures in tests to match the new contracts. |
| cmd/entire/cli/logout_test.go | Updates injected revoke function signature and assertions to match the new contract. |
| // rejection, network error). Pre-wrap, every caller had to | ||
| // fall back to string-matching, and the activity command | ||
| // papered over real failures with a misleading login hint. | ||
| return nil, fmt.Errorf("not logged in (run 'entire login' first): %w", auth.ErrNotLoggedIn) |
| // resolveDataAPIToken returns a bearer scoped for the data API. In | ||
| // split-host setups this triggers an RFC 8693 exchange against the | ||
| // auth host's STS endpoint; in single-host setups the tokenmanager | ||
| // hits the same-host shortcut and returns the core token unchanged. | ||
| // Centralised so the audience-mismatch bug that motivated this fix | ||
| // can't be reintroduced piecemeal at individual call sites. | ||
| func resolveDataAPIToken(ctx context.Context) (string, error) { | ||
| token, err := auth.TokenForResource(ctx, api.OriginOnly(api.BaseURL())) | ||
| if err != nil { | ||
| return "", fmt.Errorf("resolve API token: %w", err) | ||
| } | ||
| return token, nil | ||
| } |
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 2 potential issues.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Comment @cursor review or bugbot run to trigger another review on this PR
Reviewed by Cursor Bugbot for commit 5824022. Configure here.
| fmt.Fprintln(errW, "Not logged in. Run 'entire login' to authenticate.") | ||
| return NewSilentError(err) | ||
| } | ||
| return err |
There was a problem hiding this comment.
Activity omits cancel silent handling
Medium Severity
The refactor to resolve API tokens internally (via resolveDataAPIToken) changed error handling paths. This means runActivity now prints generic errors like context canceled instead of silencing them, and auth status no longer shows the "re-login" hint when STS rejects a revoked token, instead showing a generic error.
Additional Locations (1)
Triggered by learned rule: Map context.Canceled to NewSilentError on user cancellation
Reviewed by Cursor Bugbot for commit 5824022. Configure here.
| } | ||
|
|
||
| tokens, err := list(ctx, token) | ||
| tokens, err := list(ctx) |
There was a problem hiding this comment.
Auth status misses STS invalid token
Medium Severity
entire auth status only treats *api.HTTPError 401s as an invalid keychain token. With defaultListTokens resolving via TokenForResource first, a revoked or invalid core token in split-host setups can fail at STS with a non-HTTPError, yielding validate token: resolve API token: ... instead of the re-login message.
Reviewed by Cursor Bugbot for commit 5824022. Configure here.
5824022 to
357f576
Compare
In v2 split-host deployments (ENTIRE_AUTH_BASE_URL ≠ ENTIRE_API_BASE_URL),
the keyring stores an auth-host-issued token. Sending that token directly
to the data API's /api/v1/auth/tokens endpoint produces 401s because the
audience is wrong; the user sees a misleading "Token in keychain ... is
no longer valid" message.
Three follow-on fixes:
cmd/entire/cli/auth.go, logout.go:
defaultListTokens, defaultRevokeTokenByID, and defaultRevokeCurrentToken
now resolve a data-API-scoped bearer internally via
auth.TokenForResource(ctx, api.OriginOnly(api.BaseURL())) — RFC 8693
exchange in split-host setups, same-host shortcut in single-host
setups (so v1 single-host behavior is unchanged). The bug-prone
`token` / `callerToken` parameters are dropped from authTokenLister,
authTokenRevoker, and revokeCurrentFunc so the audience-mismatch
mistake is structurally impossible at the call sites. New helper
resolveDataAPIToken centralises the resolution.
requireSecureBaseURL now also calls auth.EnableInsecureHTTP() when
--insecure-http-auth is set, matching what NewAuthenticatedAPIClient
already does — otherwise the tokenmanager's HTTPS guard would reject
non-loopback http:// resources even after the per-command TLS check
was waived.
cmd/entire/cli/api_client.go:
Wrap the not-logged-in case as `fmt.Errorf("...: %w", auth.ErrNotLoggedIn)`
so callers can still errors.Is past the boundary (was errors.New(...)
which lost the sentinel).
cmd/entire/cli/activity_cmd.go:
Only print "Not logged in. Run 'entire login' to authenticate." when
errors.Is(err, auth.ErrNotLoggedIn). Any other error from
NewAuthenticatedAPIClient (STS rejections, network errors, malformed
env config) used to be silently re-mapped to that misleading login
hint; now real errors surface verbatim via main.go.
End-to-end verified against us.auth.entire.io: entire login, entire
auth status, entire auth list, entire search, entire activity all
succeed. Pre-existing TestExplainCmd_* failures are environmental
(.entire/settings.json enabled:false in this working tree) and
unrelated.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
357f576 to
c2d2cd6
Compare


https://entire.io/gh/entireio/cli/trails/422
Stacked on top of #1239 (
soph/auth-go). Once that merges, this PR's base will auto-rebase tomain.Summary
In v2 split-host deployments (
ENTIRE_AUTH_BASE_URL≠ENTIRE_API_BASE_URL), the keyring stores an auth-host-issued token. Sending that token directly to the data API's/api/v1/auth/tokensendpoint produces 401s because the audience is wrong; the user sees a misleading "Token in keychain ... is no longer valid" message.This PR routes
entire auth status/auth list/auth revoke/logout(server-side revoke) throughauth.TokenForResourceso they pick up a correctly-audienced bearer via the same RFC 8693 exchange thatentire search,activity,recap, anddispatchalready use. In v1 single-host the tokenmanager hits its same-host shortcut and returns the keyring token unchanged — so this is a no-op for v1 deployments.What's in the diff
cmd/entire/cli/auth.go,logout.godefaultListTokens,defaultRevokeTokenByID,defaultRevokeCurrentTokennow resolve a data-API-scoped bearer internally viaauth.TokenForResource(ctx, api.OriginOnly(api.BaseURL())).token/callerTokenparameters are dropped fromauthTokenLister,authTokenRevoker, andrevokeCurrentFuncso the audience-mismatch mistake is structurally impossible at the call sites.resolveDataAPITokencentralises the resolution.requireSecureBaseURLnow also callsauth.EnableInsecureHTTP()when--insecure-http-authis set, matchingNewAuthenticatedAPIClient— otherwise the tokenmanager's HTTPS guard rejects non-loopbackhttp://resources even after the per-command TLS check is waived.cmd/entire/cli/api_client.gofmt.Errorf("...: %w", auth.ErrNotLoggedIn)(waserrors.New(...)which lost the sentinel). Callers can nowerrors.Ispast the boundary.cmd/entire/cli/activity_cmd.goerrors.Is(err, auth.ErrNotLoggedIn). Real errors (STS rejections, network errors, malformed env config) surface verbatim viamain.goinstead of being papered over with a misleading login hint.Tests
auth_test.goandlogout_test.goupdated to match the new function-literal signatures. Assertions that checked "called with token X" are replaced with was-called booleans (the new contract is "implementation resolves its own bearer", so there's nothing to forward).Test plan
go build ./...cleanmise run lint:go— 0 issuesmise run lint:gofmtcleanmise run lint:gomodcleanTestRunAuth*/TestRunLogout*passus.auth.entire.io:entire auth status,auth listsucceed (previously 401'd)The same pre-existing
TestExplainCmd_*failures from the base branch persist — environmental (.entire/settings.jsonenabled: falsein this working tree), unrelated to this work.Known gaps / follow-up work
Internal review during development surfaced several things that didn't make it into this PR's scope; happy to address in a follow-up:
IsHTTPErrorStatus—auth.go's 401 branch only fires for*api.HTTPErrorwrappers.auth-go/stsreturns plainfmt.Errorfstrings, so an STS-stage 401 (e.g. core token revoked, invalid_client) surfaces as a raw error rather than the friendly "Token in keychain is no longer valid. Run 'entire login' to re-authenticate." message. Worth either typing the STS error upstream or a string-match workaround here.auth.go:516(list-after-revoke 401 → delete keychain) is pre-existing but mis-specified for v2 split-host — revoking an application API token by id doesn't invalidate the OAuth bearer chain, so the heuristic silently no-ops. Probably wants removal rather than papering over.NewAuthenticatedAPIClientcallers share the false-"authentication required" pattern thatactivity_cmd.gofixes —trail_cmd.go,trail_watch_cmd.go,dispatch_wizard.go,search_cmd.go. Sweep opportunity now that the sentinel-wrapping inapi_client.gomakes it trivial.defaultListTokens/defaultRevokeTokenByID/defaultRevokeCurrentTokenactually route throughTokenForResource. Covered e2e but a regression to "send raw keyring token" would passgo test ./.... Theauth.SetManagerForTestseam exists in auth-go ready for use.auth.EnableInsecureHTTPis sticky process-wide with no off switch — fine for one-shot CLI invocations, silent state leak across in-process subcommands (e.g. test harnesses, kubectl-style external command dispatcher).🤖 Generated with Claude Code
Note
Medium Risk
Touches CLI authentication/token resolution and logout/token-revocation paths; mistakes could break auth flows or change which errors are surfaced to users, especially in split-host deployments.
Overview
Fixes split-host (
ENTIRE_AUTH_BASE_URL!=ENTIRE_API_BASE_URL) auth-token management by resolving a data-API-scoped bearer viaauth.TokenForResourceforentire auth status/list/revokeand server-sidelogoutrevocation, instead of forwarding the raw keyring token.Refactors the token-management call sites to drop caller-supplied bearer parameters (making audience-mismatch harder to reintroduce), adds
resolveDataAPIToken, and aligns--insecure-http-authwith the token manager viaauth.EnableInsecureHTTP. Error handling is tightened soactivityonly shows the friendly login hint forauth.ErrNotLoggedIn, andNewAuthenticatedAPIClientpreserves theErrNotLoggedInsentinel via wrapping.Reviewed by Cursor Bugbot for commit 5824022. Configure here.