auth: active context wins over ENTIRE_AUTH_BASE_URL for control plane · Entire

auth: active context wins over ENTIRE_AUTH_BASE_URL for control plane

81e341d→main·

toothbrush·1mo ago·4 files·+67 added/-57 removed

Make ResolveControlPlaneTarget match auth status: the active context always drives host+token; ENTIRE_AUTH_BASE_URL is only the fallback when no context is active, not an override. A token minted by the context's core can't authenticate against a different host, so "override host + context identity" can't succeed — the active context is authoritative.

Drops the now-unused api.AuthBaseURLOverridden() helper.

Co-Authored-By: Claude Opus 4.8 (1M context) noreply@anthropic.com

Sessions

dbdac822b936View transcript

Changes

4

63 unmodified lines

return NormalizeOriginURL(raw)
}

// AuthBaseURLOverridden reports whether ENTIRE_AUTH_BASE_URL is explicitly
// set (non-empty after trimming). Control-plane host resolution treats an
// explicit override as unconditional: it pins the core to that origin and
// skips the active-context lookup, so split-host / local-dev invocations that
// already set this var keep their exact behaviour.
func AuthBaseURLOverridden() bool {
    return strings.TrimSpace(os.Getenv(AuthBaseURLEnvVar)) != ""
}

// IsSplitHost reports whether the CLI is configured for split-host —
// i.e. ENTIRE_AUTH_BASE_URL points at a different origin than the data
// API. Both sides are canonicalised via NormalizeOriginURL before

Mcmd/entire/cli/api/base_url.go-9

22 unmodified lines

// 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:
//
// The core. Precedence (matching `auth status`):
//
// 1. ENTIRE_AUTH_BASE_URL explicitly set -> that origin verbatim, bearer via
//    the singleton manager (TokenForResource), exactly as before. The env
//    var stays an unconditional override so split-host / local-dev
//    invocations are untouched.
// 2. otherwise the active contexts.json login -> its CoreURL, with a
//    per-context refreshing bearer (silent JWT re-mint). This is what makes
// 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.
// 3. no active context -> the configured default origin + TokenForResource,
//    the pre-contexts behaviour for users who never ran `entire auth use`.
// 2. no active context -> the configured auth origin (ENTIRE_AUTH_BASE_URL or
//    the default) + TokenForResource, the pre-contexts fallback.
//
// ENTIRE_AUTH_BASE_URL 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 override host but the context's identity" can't succeed. The active
// context always wins when present.
func ResolveControlPlaneTarget() (ControlPlaneTarget, error) {
    if api.AuthBaseURLOverridden() {
        return staticControlPlaneTarget(), nil
    }

c, ok, err := activeContext()
    if err != nil {
        return ControlPlaneTarget{}, err
    }
    return ControlPlaneTarget{CoreURL: strings.TrimRight(c.CoreURL, "/"), TokenSource: src}, nil
}

// staticControlPlaneTarget is the pre-contexts path: dial the configured 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. Used for an explicit ENTIRE_AUTH_BASE_URL override and as the
// no-active-context fallback.
// staticControlPlaneTarget is the no-active-context fallback: dial the
// configured auth origin (ENTIRE_AUTH_BASE_URL or the default) 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

Mcmd/entire/cli/auth/control_plane.go+14/-18

24 unmodified lines

// An explicit ENTIRE_AUTH_BASE_URL pins the core to that origin and skips the
// active-context lookup entirely — the override is unconditional.
func TestResolveControlPlaneTarget_EnvOverrideWins(t *testing.T) {
// 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()
    t.Setenv("ENTIRE_CONFIG_DIR", configDir)
    t.Setenv(api.AuthBaseURLEnvVar, "https://override.example")

// An active context on a *different* core must be ignored under override.
    writeActiveContext(t, configDir, "ctx", "https://other-core.example", "alice", "svc")
    const coreURL = "https://other-core.example"
    writeActiveContext(t, configDir, "ctx", coreURL, "alice", "svc")

target, err := ResolveControlPlaneTarget()
    if err != nil {
        t.Fatalf("ResolveControlPlaneTarget: %v", err)
    }
    if want := api.AuthBaseURL(); target.CoreURL != want {
        t.Fatalf("CoreURL = %q, want the override origin %q (not the context core)", target.CoreURL, want)
    }
    if target.CoreURL != coreURL {
        t.Fatalf("CoreURL = %q, want the active context's core %q (env is only a fallback)", target.CoreURL, coreURL)
    }
}

// With no override and no active context, the target falls back to the
// configured default auth origin (pre-contexts behaviour).
func TestResolveControlPlaneTarget_NoContextFallsBackToDefault(t *testing.T) {
    configDir := t.TempDir() // empty: no contexts.json
    t.Setenv("ENTIRE_CONFIG_DIR", configDir)
    t.Setenv(api.AuthBaseURLEnvVar, "")
// 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
        t.Setenv("ENTIRE_CONFIG_DIR", configDir)
        t.Setenv(api.AuthBaseURLEnvVar, "")

target, err := ResolveControlPlaneTarget()
        if err != nil {
            t.Fatalf("ResolveControlPlaneTarget: %v", err)
        }
        if want := api.AuthBaseURL(); target.CoreURL != want {
            t.Fatalf("CoreURL = %q, want the default auth origin %q", target.CoreURL, want)
        }
        target, err := ResolveControlPlaneTarget()
        if err != nil {
            t.Fatalf("ResolveControlPlaneTarget: %v", err)
        }
        if want := api.AuthBaseURL(); target.CoreURL != want {
            t.Fatalf("CoreURL = %q, want the default auth origin %q", target.CoreURL, want)
        }
    })

t.Run("env value when set", func(t *testing.T) {
        configDir := t.TempDir()
        t.Setenv("ENTIRE_CONFIG_DIR", configDir)
        t.Setenv(api.AuthBaseURLEnvVar, "https://fallback.example")

target, err := ResolveControlPlaneTarget()
        if err != nil {
            t.Fatalf("ResolveControlPlaneTarget: %v", err)
        }
        if want := api.AuthBaseURL(); target.CoreURL != want {
            t.Fatalf("CoreURL = %q, want the env fallback origin %q", target.CoreURL, want)
        }
    })
}

Mcmd/entire/cli/auth/control_plane_test.go+38/-20

Control plane (done — this slice)

The host is a core, so there is no discovery. coreapi.New() consults auth.ResolveControlPlaneTarget(), which mirrors auth status:

  1. ENTIRE_AUTH_BASE_URL set → that origin, verbatim; bearer via the singleton token manager (TokenForResource). The env var is an unconditional override, so split-host / local-dev invocations are untouched.
  2. else active context → its CoreURL, with a per-context refreshing bearer (auth.NewRefreshingLoginProvider): the token manager is keyed on c.CoreURL as issuer, so store reads and refresh/STS hit the right core, and an expired access token is silently re-minted from the stored refresh token. This is what makes entire auth use <ctx> actually retarget org/repo/project/grant.
  3. else (no active context) → the configured default origin + TokenForResource — the pre-contexts behaviour.
  4. else (no active context) → the configured auth origin (ENTIRE_AUTH_BASE_URL or the default) + TokenForResource — the pre-contexts fallback.

ENTIRE_AUTH_BASE_URL is the fallback host, not an override: a token minted by the active context's core can't authenticate against a different host, so the active context always wins when present. (At login time the env var still chooses where to authenticate, and the resulting context's CoreURL is that host — so local-dev / split-host setups keep working.)

Key files: cmd/entire/cli/auth/control_plane.go (resolver), cmd/entire/cli/auth/refresh.go (per-context refreshing provider), the activity / search / trail / dispatch constructors (NewAuthenticatedAPIClient, dispatch.NewCloudClient, search.Search).

ENTIRE_AUTH_BASE_URL / ENTIRE_API_BASE_URL remain explicit overrides: when set they short-circuit discovery; discovery only runs when they are unset. ENTIRE_API_BASE_URL names the resource host to dial (the discovery target); the context is then chosen from the issuers that host advertises. It does not need to be paired with ENTIRE_AUTH_BASE_URL once discovery exists — that is the whole point of the well-known.