Skip to content
25 changes: 18 additions & 7 deletions internal/cli/auth.go
Original file line numberDiff line numberDiff line change
Expand Up@@ -411,7 +411,13 @@ func newAuthStatusCmd() *cobra.Command {
prof := cfg.Current()
p.Section("tracebloc auth")
p.Field("status", "signed in")
p.Field("backend", cfg.CurrentEnv)
// sessionEnv, not the raw stored string: this line is the human-facing
// answer to "which backend am I on?", and it must be the same answer
// --check computes and the same one authedClient dials. Printing the
// stored value let `auth status` say `Dev` while every request went to
// dev — a status command that disagrees with the client is worse than
// no status command.
p.Field("backend", sessionEnv(cfg))
if prof.Email != "" {
p.Field("account", prof.Email)
}
Expand DownExpand Up@@ -442,7 +448,7 @@ func newAuthStatusCmd() *cobra.Command {
// (IsSilentError) so main() prints nothing.
//
// The target env is resolved exactly like `login` (--env, then $CLIENT_ENV, then
// prod), and must match the signed-in CurrentEnv — otherwise the probe would OK a
// prod), and must match the signed-in env as sessionEnv resolves it — otherwise the probe would OK a
// stale session for the wrong backend and the installer would skip the very
// `login` that switches env, provisioning into the wrong account (RFC-0001 §10).
func runAuthCheck(ctx context.Context, p *ui.Printer, envFlag string) error {
Expand All@@ -454,18 +460,23 @@ func runAuthCheck(ctx context.Context, p *ui.Printer, envFlag string) error {
return &exitError{code: exitFailure}
}
target := api.ResolveEnv(envFlag)
if !cfg.SignedIn() || cfg.CurrentEnv != target {
// Compare the RESOLVED session env, not the raw cfg.CurrentEnv: target comes
// out of api.ResolveEnv already normalised, so comparing it against the stored
// string made this the one place a `"current_env": "Dev"` config failed a probe
// for the session it is actually signed in to.
signedIn := sessionEnv(cfg)
if !cfg.SignedIn() || signedIn != target {
if p.Verbose() {
if cfg.SignedIn() && cfg.CurrentEnv != target {
p.Hintf("Signed in to %q, but this run targets %q — run `tracebloc login`.", cfg.CurrentEnv, target)
if cfg.SignedIn() && signedIn != target {
p.Hintf("Signed in to %q, but this run targets %q — run `tracebloc login`.", signedIn, target)
} else {
p.Hintf("Not signed in. Run `tracebloc login`.")
}
}
return &exitError{code: exitFailure}
}
// Signed in AND CurrentEnv == target: probe it. authedClient() builds the client
// for sessionEnv (== CurrentEnv == target) with the stored token — reuse it and
// Signed in AND the resolved session env == target: probe it. authedClient()
// builds the client for sessionEnv (== the value just compared) with the stored token — reuse it and
// discard its message (the exit code is the contract here).
client, _, err := authedClient()
if err != nil {
Expand Down
24 changes: 19 additions & 5 deletions internal/cli/client.go
Original file line numberDiff line numberDiff line change
Expand Up@@ -143,12 +143,26 @@ func clientPrompter() prompter {
}

// sessionEnv resolves the backend env for the signed-in session: the env saved
// at login, falling back (legacy / empty config) to $CLIENT_ENV then prod. Shared
// by authedClient and logout so every authenticated call — including the revoke
// on sign-out — talks to the host the token was actually issued for.
// at login, falling back (legacy / empty config) to $CLIENT_ENV then prod.
//
// THE ONLY PLACE THAT DERIVES A SESSION ENV FROM A CONFIG. Every caller that
// wants "which backend is this signed-in session on?" — authedClient, logout's
// revoke, `cluster doctor`, `auth status --check`, the telemetry label — goes
// through here, so the answer cannot differ by caller. Reading cfg.CurrentEnv
// directly is the bug this function exists to prevent: it silently drops the
// $CLIENT_ENV fallback, and it skips the normalisation below.
//
// The result is normalised (trimmed, lower-cased) to match api.ResolveEnv, which
// lower-cases both its explicit argument and $CLIENT_ENV. Returning cfg.CurrentEnv
// verbatim made this the one env-resolving function in the CLI whose output was
// not normalised: harmless where the value only reaches api.BaseURL (which
// lower-cases again), but a false negative anywhere the value is COMPARED — a
// config carrying `"current_env": "Dev"` (migrateV1 stores a v1 `env` verbatim,
// and the file is hand-written in fixtures) failed `auth status --check --env dev`
// against a session that works perfectly.
func sessionEnv(cfg *config.Config) string {
if cfg.CurrentEnv != "" {
return cfg.CurrentEnv
if e := strings.ToLower(strings.TrimSpace(cfg.CurrentEnv)); e != "" {
return e
}
return api.ResolveEnv("")
}
Expand Down
6 changes: 5 additions & 1 deletion internal/cli/doctor.go
Original file line numberDiff line numberDiff line change
Expand Up@@ -111,7 +111,11 @@ func runClusterDoctor(
// an error (5xx/403/decode) is a tracebloc-side problem, distinct from a
// network failure to reach it at all. Conflating the two would blame the
// user's network (and hand them a proxy remedy) for tracebloc's own error.
apiClient := newAPIClient(cfg.CurrentEnv)
// sessionEnv, not cfg.CurrentEnv: the session probe must target the same host
// authedClient would, or `doctor` reports on a backend no other command talks
// to. Reading CurrentEnv directly drops sessionEnv's $CLIENT_ENV fallback and
// its normalisation — a second resolution of the same question.
apiClient := newAPIClient(sessionEnv(cfg))
apiClient.Token = cfg.Current().Token
if _, werr := apiClient.WhoAmI(ctx); werr != nil {
var ae *api.APIError
Expand Down
Loading
Loading