auth: make `auth status` context-aware; hit /me on the active core · Entire
auth: make auth status context-aware; hit /me on the active core
97207ef·
toothbrush·1mo ago·4 files·+205 added/-150 removed
auth status queried /me against the static api.AuthBaseURL(), so with an active context on a different core (e.g. auth use eu.auth.entire.io while AuthBaseURL defaults to us.*) it sent the context's token to the wrong core and got a 401 — surfaced as a raw ogen decode dump because the 401 body was text/plain.
- Resolve the active contexts.json context first (resolveStatusTarget): use its CoreURL + session token, falling back to AuthBaseURL + the legacy keyring entry only when no context is active.
auth usenow retargets status. "Logged in to" reflects the active context. - Add coreapi.NewWithBearer(coreURL, token) to hit a specific login server with a fixed bearer (no STS), used by status's /me.
- Harden isKeychainTokenRejected: a non-JSON 401 (ogen "decode response: ... (code 401)") now maps to the friendly re-login hint, not a raw dump.
- TLS-guard the resolved context core URL before sending the token.
Sessions
Transcript: 1385b003c7a8
Changes
4
cmd/entire/cli
Mauth.go+75/-35
Mauth_context_test.go+26
Mauth_test.go+78/-115
internal/coreapi
Mclient.go+26
118 unmodified lines
if errors.Is(err, auth.ErrNotLoggedIn) { return true } // A 401 whose body isn't JSON (e.g. a gateway returning text/plain) fails // the ogen typed decode, so it never becomes an ErrorModelStatusCode — it // arrives as a decode error whose message carries "(code 401)". Match that // so the user still gets the re-login hint, not a raw decode dump. if strings.Contains(err.Error(), "code 401") { return true } return strings.Contains(err.Error(), "token exchange: status 4") }
// statusTarget is the resolved core `entire auth status` should query: the active context's CoreURL + its session token, or (no active context) the configured AuthBaseURL + legacy keyring entry.
type statusTarget struct {
coreURL string
token string
activeContext string // "" when falling back to the legacy entry
totalContexts int
}
// resolveStatusTarget picks the core + token for `entire auth status`. The active contexts.json context wins (so `auth use` retargets status onto that login server); otherwise it falls back to the legacy keyring entry keyed by the configured auth host.
func resolveStatusTarget(store tokenStore, listContexts contextsProvider, fallbackBaseURL string) statusTarget {
all, current, err := listContexts()
total := 0
if err == nil {
total = len(all)
for _, c := range all {
if c.Name != current || c.CoreURL == "" {
continue
}
if tok, terr := auth.LoginTokenForContext(c); terr == nil && tok != "" {
return statusTarget{coreURL: c.CoreURL, token: tok, activeContext: c.Name, totalContexts: total}
}
}
}
tok, gerr := store.GetToken(fallbackBaseURL)
if gerr != nil {
tok = "" // best-effort: a keyring read failure just reads as "no token"
}
return statusTarget{coreURL: fallbackBaseURL, token: tok, totalContexts: total}
}
// runAuthStatus reports auth state without listing server-side sessions: GET /me validates the token and supplies the profile header, and the active login context is read locally. (Session listing/revocation lives on entire-core and is reached only by logout — see newSessionsClient.)
func runAuthStatus(ctx context.Context, w io.Writer, store tokenStore, fetchProfile profileFetcher, listContexts contextsProvider, baseURL string) error {
token, err := store.GetToken(baseURL)
if err != nil {
return fmt.Errorf("read keychain: %w", err)
}
if token == "" {
fmt.Fprintf(w, "Not logged in to %s\n", baseURL)
return nil
}
profile, err := fetchProfile(ctx)
profile, err := fetchProfile(ctx, t.coreURL, t.token)
if err != nil {
if isKeychainTokenRejected(err) {
fmt.Fprintf(w, "Token in keychain for %s is no longer valid.\n", baseURL)
fmt.Fprintf(w, "Login for %s is no longer valid.\n", t.coreURL)
fmt.Fprintln(w, "Run 'entire login' to re-authenticate.")
return nil
}
return fmt.Errorf("validate token: %w", err)
}
fmt.Fprintf(w, "Logged in to %s\n", baseURL)
fmt.Fprintf(w, "Logged in to %s\n", t.coreURL)
writeProfileLines(w, profile)
// Local context info is informational; a read failure shouldn't fail the command, so on error we just skip the context lines.
all, current, ctxErr := listContexts()
if ctxErr == nil && current != "" {
fmt.Fprintf(w, " %-9s %s\n", "Context:", current)
if t.activeContext != "" {
fmt.Fprintf(w, " %-9s %s\n", "Context:", t.activeContext)
}
fmt.Fprintf(w, " %-9s %s\n", "Token:", "stored in OS keychain")
if ctxErr == nil && len(all) > 1 {
if t.totalContexts > 1 {
fmt.Fprintln(w)
fmt.Fprintf(w, "%d login contexts saved; run 'entire auth contexts' to list or 'entire auth use <name>' to switch.\n", len(all))
}
}
}
}