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

// 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
    })
}