api: delete AuthBaseURL(); the env var is gate-only (COR-393) · Entire
api: delete AuthBaseURL(); the env var is gate-only (COR-393)
de7206b→main·
toothbrush·1mo ago·3 files·+17 added/-86 removed
No reader of ENTIRE_AUTH_BASE_URL remains: every token path resolves its core from contexts.json or /.well-known discovery, and login takes --server. The constant DefaultAuthBaseURL stays as the --server default, and AuthBaseURLEnvVar stays only for RejectRemovedAuthEnv. NewAuthenticatedAPIClient now TLS-checks just the data origin it actually dials; the exchange leg is guarded by the per-context token manager.
Co-Authored-By: Claude Fable 5 noreply@anthropic.com
Sessions
c981a8816bc0View transcript
[?
Auth Refactor: Eliminate Static FallbacksClaude Code·Fable 5.[1m]·4 steps](/content/gh/entireio/cli/session/e6146684-ebfa-4f57-beff-34194cac8c2a#timeline-c981a8816bc0/index.html)
Changes
3
cmd/entire/cli
api
Mbase_url.go+6/-30
Mbase_url_test.go-32
Mapi_client.go+11/-24
14 unmodified lines
15
16
17
18
19
20
18
19
20
21
22
23
24
26
27
28
29
25
26
27
28
29
30
3 unmodified lines
34
35
36
39
40
37
38
39
40
11 unmodified lines
52
53
54
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
55
56
57
14 unmodified lines
// DefaultBaseURL is the production Entire API origin.
DefaultBaseURL = "https://entire.io"
// DefaultAuthBaseURL is the production Entire auth origin (device flow,
// auth-token management, keyring key). The CLI is split-host by default:
// auth on us.auth.entire.io, data on entire.io.
// DefaultAuthBaseURL is the production Entire login server — the
// default for `entire login --server`.
DefaultAuthBaseURL = "https://us.auth.entire.io"
// BaseURLEnvVar overrides the Entire API origin for local development.
BaseURLEnvVar = "ENTIRE_API_BASE_URL"
// AuthBaseURLEnvVar overrides only the auth/login origin (device flow,
// auth-tokens management, keyring key). Falls back to DefaultAuthBaseURL
// when unset; local-dev and single-host deployments must set this
// alongside ENTIRE_API_BASE_URL.
// AuthBaseURLEnvVar is the retired auth-origin override. Nothing reads
// its value anymore — RejectRemovedAuthEnv fails every command when it
// is set, pointing at `entire login --server`.
AuthBaseURLEnvVar = "ENTIRE_AUTH_BASE_URL"
schemeHTTP = "http"
3 unmodified lines
// RejectRemovedAuthEnv returns an error when ENTIRE_AUTH_BASE_URL is set
// at all (even empty). The variable is retired in favour of
// `entire login --server`; failing loudly beats silently ignoring an
// override the operator believes is in effect. The remaining internal
// AuthBaseURL() reads only ever see the default once this gate has run.
// override the operator believes is in effect.
func RejectRemovedAuthEnv() error {
if _, ok := os.LookupEnv(AuthBaseURLEnvVar); ok {
return fmt.Errorf("%s is no longer supported; unset it, and use `entire login --server <url>` to log in to a non-default login server", AuthBaseURLEnvVar)
}
return DefaultBaseURL
}
// AuthBaseURL returns the origin used for the device-flow login, auth-token
// management endpoints, and the keyring key under which the bearer token is
// stored. ENTIRE_AUTH_BASE_URL takes precedence; otherwise it falls back to
// DefaultAuthBaseURL (split-host by default).
//
// The result is canonicalised — lowercased scheme/host, default port stripped,
// path/query/fragment dropped, trailing slash collapsed — so the value that
// flows into store.SaveToken keys matches what tokenmanager.New emits after
// its own NormalizeOriginURL pass. Without this, a user setting
// ENTIRE_AUTH_BASE_URL=https://AUTH.example.com:443/ would log in successfully
// (saved under the raw form) but every subsequent data-API command would
// resolve "not logged in" because the manager probes under the normalised
// "https://auth.example.com".
func AuthBaseURL() string {
raw := strings.TrimSpace(os.Getenv(AuthBaseURLEnvVar))
if raw == "" {
raw = DefaultAuthBaseURL
}
return NormalizeOriginURL(raw)
}
// ResolveURL joins an API-relative path against the effective base URL.
func ResolveURL(path string) (string, error) {
return ResolveURLFromBase(BaseURL(), path)
}
Mcmd/entire/cli/api/base_url.go+6/-30
54 unmodified lines
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
58
59
60
54 unmodified lines
}
}
func TestAuthBaseURL_FallsBackToDefault(t *testing.T) {
// Default is split-host: an unset ENTIRE_AUTH_BASE_URL must NOT inherit
// from ENTIRE_API_BASE_URL — local-dev that only sets the data host
// would otherwise silently point auth at a host that can't mint tokens.
t.Setenv(BaseURLEnvVar, "https://example.test")
t.Setenv(AuthBaseURLEnvVar, "")
if got := AuthBaseURL(); got != DefaultAuthBaseURL {
t.Fatalf("AuthBaseURL() = %q, want %q", got, DefaultAuthBaseURL)
}
}
func TestAuthBaseURL_OverrideTakesPrecedence(t *testing.T) {
t.Setenv(BaseURLEnvVar, "https://data.example.test")
t.Setenv(AuthBaseURLEnvVar, " https://auth.example.test/ ")
if got := AuthBaseURL(); got != "https://auth.example.test" {
t.Fatalf("AuthBaseURL() = %q, want trimmed/normalized override", got)
}
}
func TestAuthBaseURL_CanonicalisesScheme_HostCase_DefaultPort(t *testing.T) {
// Same canonicalisation tokenmanager.New applies internally — must match
// or the keyring key login wrote diverges from the one the manager later
// reads, producing spurious "not logged in" errors on every data-API call.
t.Setenv(AuthBaseURLEnvVar, "HTTPS://AUTH.example.com:443/");
if got := AuthBaseURL(); got != "https://auth.example.com" {
t.Fatalf("AuthBaseURL() = %q, want canonicalised origin", got)
}
}
func TestNormalizeOriginURL(t *testing.T) {
t.Parallel()
Mcmd/entire/cli/api/base_url_test.go-32
9 unmodified lines
10
11
12
13
14
15
16
17
18
19
20
13
14
15
16
17
23
24
25
18
19
20
21
27
22
23
24
30
31
32
33
34
35
36
37
38
25
26
27
28
29
30
43
44
45
31
32
33
34
35
9 unmodified lines
)
// NewAuthenticatedAPIClient creates an API client targeting api.BaseURL()
// (the data API origin) carrying a token valid for that audience.
//
// Resolution: looks up the core token from the keyring, then either uses
// it directly (single-host setup, or when the core token's `aud` already
// covers api.BaseURL()) or performs an RFC 8693 token exchange against
// the auth host to obtain a token scoped to the data API. Exchanged
// tokens are cached in-memory keyed off the wire-affecting fields of
// the request — see tokenmanager.cacheKey for the precise key shape.
// (the data API origin) carrying a token valid for that audience, minted by
// exchanging the matching login context's JWT at its own core (see
// auth.ResolveDataAPIToken).
//
// Pass insecureHTTP=true to allow plain HTTP base URLs for local
// development. Both api.BaseURL() and api.AuthBaseURL() are validated:
// the bearer travels to the data host on resource requests, and the
// core token travels to the auth host during the exchange step.
// development. Only the data origin is checked here — the bearer travels
// there on resource requests; the exchange leg is guarded by the
// per-context token manager (https required outside loopback/opt-in).
func NewAuthenticatedAPIClient(ctx context.Context, insecureHTTP bool) (*api.Client, error) {
dataURL, authURL := api.BaseURL(), api.AuthBaseURL();
dataURL := api.BaseURL();
if insecureHTTP {
auth.EnableInsecureHTTP()
} else {
if err := api.RequireSecureURL(dataURL); err != nil {
return nil, fmt.Errorf("base URL check: %w", err)
}
if authURL != dataURL {
if err := api.RequireSecureURL(authURL); err != nil {
return nil, fmt.Errorf("auth base URL check: %w", err)
}
}
} else if err := api.RequireSecureURL(dataURL); err != nil {
return nil, fmt.Errorf("base URL check: %w", err)
}
// ResolveDataAPIToken discovers which login context the data host trusts
// (via its /.well-known/entire-api.json) and exchanges that context's
// token for the advertised audience, falling back to static resolution
// when the host doesn't advertise discovery. It normalises dataURL to an
// origin internally.
// token for the advertised audience. It normalises dataURL to an origin
// internally.
token, err := auth.ResolveDataAPIToken(ctx, dataURL)
if err != nil {
if errors.Is(err, auth.ErrNotLoggedIn) {