auth: error instead of static fallback when no context is active (COR-393) · Entire
auth: error instead of static fallback when no context is active (COR-393)
3381355→main · → toothbrush · 1mo ago · 3 files · +37 added / -99 removed
ResolveControlPlaneTarget loses its no-context fallback (static AuthBaseURL target + TokenForResource): a control-plane command without a login has no identity to act as, so it now errors wrapping ErrNotLoggedIn and callers render the entire login hint.
Co-Authored-By: Claude Fable 5 noreply@anthropic.com
Sessions
1cbc7d4b5116 View transcript
Changes
3
cmd/entire/cli/auth
Mcontrol_plane.go +11 / -35
Mcontrol_plane_test.go +19 / -55
internal/coreapi
- Mclient.go +7 / -9
// ResolveControlPlaneTarget chooses which core the control-plane commands talk
to and how their bearer is obtained. The control-plane host *is* a core, so
there is no /.well-known discovery here — the active context already names
the core. Precedence (matching `auth status`):
// there is no /.well-known discovery here — the active context names the core,
// which is what makes `entire auth use <ctx>` retarget the control plane onto
// that login server. The bearer is a per-context refreshing provider (silent
// JWT re-mint from the stored refresh token).
// 1. the active contexts.json login -> its CoreURL, with a per-context
// refreshing bearer (silent JWT re-mint). This is what makes
// `entire auth use <ctx>` retarget the control plane onto that core.
// 2. no active context -> the default auth origin + TokenForResource,
// the pre-contexts fallback.
// The default auth origin is the fallback host, not an override: a token
// minted by the active context's core can't authenticate against a different
// host, so "use the fallback host but the context's identity" can't succeed.
// The active context always wins when present.
// No active context means not logged in: the error wraps ErrNotLoggedIn so
// callers render the `entire login` hint. There is no fallback host — a
// control-plane command without a login has no identity to act as.
func ResolveControlPlaneTarget() (ControlPlaneTarget, error) {
c, ok, err := activeContext()
if err != nil {
return ControlPlaneTarget{}, err
}
if !ok {
return staticControlPlaneTarget(), nil
return ControlPlaneTarget{}, &reauthError{
msg: "not logged in; run `entire login`",
sentinel: ErrNotLoggedIn,
}
}
// The refreshing provider keys its own token manager on c.CoreURL as the
// issuer, so its store reads and STS/refresh both target the right core —
// the bug the singleton manager (pinned to AuthBaseURL) has when the
// active context lives on a different core.
src, err := NewRefreshingLoginProvider(c, nil, insecureHTTPEnabled() || isLoopbackHTTP(c.CoreURL))
if err != nil {
return ControlPlaneTarget{}, fmt.Errorf("build token source for context %q: %w", c.Name, err)
}
return ControlPlaneTarget{CoreURL: strings.TrimRight(c.CoreURL, "/"), TokenSource: src}, nil
}
// staticControlPlaneTarget is the no-active-context fallback: dial the
// default auth origin and resolve the
// bearer through the singleton manager, which performs an RFC 8693 exchange
// when the stored token's audience doesn't cover the core.
func staticControlPlaneTarget() ControlPlaneTarget {
base := strings.TrimRight(api.AuthBaseURL(), "/")
// The exchange's resource must be the bare origin; OriginOnly strips any
// path/query so the audience the manager keys on matches the server's.
resource := api.OriginOnly(base)
return ControlPlaneTarget{
CoreURL: base,
TokenSource: func(ctx context.Context) (string, error) {
return TokenForResource(ctx, resource)
},
}
}
// activeContext returns the active contexts.json login and ok=true, or
// ok=false when there is no current context or it carries no CoreURL (an
// unusable pointer we treat as "no active context" rather than dialing an
// The active context wins even when ENTIRE_AUTH_BASE_URL is set to a different
// origin: the env var is a fallback host, not an override (a token from the
// context's core can't authenticate against the env host anyway).
func TestResolveControlPlaneTarget_ActiveContextBeatsEnv(t *testing.T) {
configDir := t.TempDir()
//... continued
}
// With no active context, the target falls back to the configured auth origin
// — the default when ENTIRE_AUTH_BASE_URL is unset, or the env value when set
// (the env var is the fallback host).
func TestResolveControlPlaneTarget_NoContextFallsBackToAuthBaseURL(t *testing.T) {
t.Run("default when env unset", func(t *testing.T) {
configDir := t.TempDir() // empty: no contexts.json
//... continued
})
}