@@ -119,6 +119,13 @@ func isKeychainTokenRejected(err error) bool {
119119 if errors .Is (err , auth .ErrNotLoggedIn ) {
120120 return true
121121 }
122+ // A 401 whose body isn't JSON (e.g. a gateway returning text/plain) fails
123+ // the ogen typed decode, so it never becomes an ErrorModelStatusCode — it
124+ // arrives as a decode error whose message carries "(code 401)". Match that
125+ // so the user still gets the re-login hint, not a raw decode dump.
126+ if strings .Contains (err .Error (), "code 401" ) {
127+ return true
128+ }
122129 return strings .Contains (err .Error (), "token exchange: status 4" )
123130}
124131
@@ -160,8 +167,15 @@ func newAuthStatusCmd() *cobra.Command {
160167 if err := requireSecureBaseURL (insecureHTTPAuth ); err != nil {
161168 return err
162169 }
163- return runAuthStatus (cmd .Context (), cmd .OutOrStdout (),
164- auth .NewContextStore (), defaultFetchProfile , auth .Contexts , api .AuthBaseURL ())
170+ target := resolveStatusTarget (auth .NewContextStore (), auth .Contexts , api .AuthBaseURL ())
171+ // We send the session token to target.coreURL; enforce TLS on it
172+ // too (it may differ from AuthBaseURL when a context is active).
173+ if ! insecureHTTPAuth {
174+ if err := api .RequireSecureURL (target .coreURL ); err != nil {
175+ return fmt .Errorf ("context core URL check: %w" , err )
176+ }
177+ }
178+ return runAuthStatus (cmd .Context (), cmd .OutOrStdout (), defaultFetchProfile , target )
165179 },
166180 }
167181 addInsecureHTTPAuthFlag (cmd , & insecureHTTPAuth )
@@ -178,23 +192,57 @@ type authProfile struct {
178192 ProviderUserID string
179193}
180194
181- // profileFetcher fetches the logged-in user's profile via GET /me on the core
182- // API . Injected so status stays unit-testable without a live core.
183- type profileFetcher func (ctx context.Context ) (* authProfile , error )
195+ // profileFetcher fetches a user's profile via GET /me on coreURL, authenticated
196+ // with token . Injected so status stays unit-testable without a live core.
197+ type profileFetcher func (ctx context.Context , coreURL , token string ) (* authProfile , error )
184198
185199// contextsProvider returns the stored login contexts and the active context
186- // name, for the local-context lines in `entire auth status`. Injected for
187- // testability; production wires auth.Contexts.
200+ // name. Injected for testability; production wires auth.Contexts.
188201type contextsProvider func () ([]* contexts.Context , string , error )
189202
190- // defaultFetchProfile fetches the current user's profile from the core API's
191- // GET /me. It doubles as the liveness check for `entire auth status`: a 401
192- // (or an expired login that can't be exchanged) means the stored token is no
193- // longer usable, which isKeychainTokenRejected maps to a re-login hint.
194- func defaultFetchProfile (ctx context.Context ) (* authProfile , error ) {
195- client , err := coreapi .New ()
203+ // statusTarget is the resolved core `entire auth status` should query: the
204+ // active context's CoreURL + its session token, or (no active context) the
205+ // configured AuthBaseURL + legacy keyring entry.
206+ type statusTarget struct {
207+ coreURL string
208+ token string
209+ activeContext string // "" when falling back to the legacy entry
210+ totalContexts int
211+ }
212+
213+ // resolveStatusTarget picks the core + token for `entire auth status`. The
214+ // active contexts.json context wins (so `auth use` retargets status onto that
215+ // login server); otherwise it falls back to the legacy keyring entry keyed by
216+ // the configured auth host.
217+ func resolveStatusTarget (store tokenStore , listContexts contextsProvider , fallbackBaseURL string ) statusTarget {
218+ all , current , err := listContexts ()
219+ total := 0
220+ if err == nil {
221+ total = len (all )
222+ for _ , c := range all {
223+ if c .Name != current || c .CoreURL == "" {
224+ continue
225+ }
226+ if tok , terr := auth .LoginTokenForContext (c ); terr == nil && tok != "" {
227+ return statusTarget {coreURL : c .CoreURL , token : tok , activeContext : c .Name , totalContexts : total }
228+ }
229+ }
230+ }
231+ tok , gerr := store .GetToken (fallbackBaseURL )
232+ if gerr != nil {
233+ tok = "" // best-effort: a keyring read failure just reads as "no token"
234+ }
235+ return statusTarget {coreURL : fallbackBaseURL , token : tok , totalContexts : total }
236+ }
237+
238+ // defaultFetchProfile fetches a user's profile from coreURL's GET /me with the
239+ // given bearer. It doubles as the liveness check for `entire auth status`: a
240+ // 401 (or an expired login) means the token is no longer usable, which
241+ // isKeychainTokenRejected maps to a re-login hint.
242+ func defaultFetchProfile (ctx context.Context , coreURL , token string ) (* authProfile , error ) {
243+ client , err := coreapi .NewWithBearer (coreURL , token )
196244 if err != nil {
197- return nil , fmt .Errorf ("connect to Entire control plane : %w" , err )
245+ return nil , fmt .Errorf ("connect to %s : %w" , coreURL , err )
198246 }
199247 me , err := client .GetMe (ctx )
200248 if err != nil {
@@ -213,44 +261,36 @@ func defaultFetchProfile(ctx context.Context) (*authProfile, error) {
213261}
214262
215263// runAuthStatus reports auth state without listing server-side sessions: GET
216- // /me validates the token and supplies the profile header, and the active
217- // login context is read locally. (Session listing/revocation lives on
218- // entire-core and is reached only by logout — see newSessionsClient.)
219- func runAuthStatus (ctx context.Context , w io.Writer , store tokenStore , fetchProfile profileFetcher , listContexts contextsProvider , baseURL string ) error {
220- token , err := store .GetToken (baseURL )
221- if err != nil {
222- return fmt .Errorf ("read keychain: %w" , err )
223- }
224- if token == "" {
225- fmt .Fprintf (w , "Not logged in to %s\n " , baseURL )
264+ // /me on the target core validates the token and supplies the profile header,
265+ // and the active login context is shown locally. (Session listing/revocation
266+ // lives on entire-core and is reached only by logout — see newSessionsClient.)
267+ func runAuthStatus (ctx context.Context , w io.Writer , fetchProfile profileFetcher , t statusTarget ) error {
268+ if t .token == "" {
269+ fmt .Fprintf (w , "Not logged in to %s\n " , t .coreURL )
226270 fmt .Fprintln (w , "Run 'entire login' to authenticate." )
227271 return nil
228272 }
229273
230- profile , err := fetchProfile (ctx )
274+ profile , err := fetchProfile (ctx , t . coreURL , t . token )
231275 if err != nil {
232276 if isKeychainTokenRejected (err ) {
233- fmt .Fprintf (w , "Token in keychain for %s is no longer valid.\n " , baseURL )
277+ fmt .Fprintf (w , "Login for %s is no longer valid.\n " , t . coreURL )
234278 fmt .Fprintln (w , "Run 'entire login' to re-authenticate." )
235279 return nil
236280 }
237281 return fmt .Errorf ("validate token: %w" , err )
238282 }
239283
240- fmt .Fprintf (w , "Logged in to %s\n " , baseURL )
284+ fmt .Fprintf (w , "Logged in to %s\n " , t . coreURL )
241285 writeProfileLines (w , profile )
242-
243- // Local context info is informational; a read failure shouldn't fail the
244- // command, so on error we just skip the context lines.
245- all , current , ctxErr := listContexts ()
246- if ctxErr == nil && current != "" {
247- fmt .Fprintf (w , " %-9s %s\n " , "Context:" , current )
286+ if t .activeContext != "" {
287+ fmt .Fprintf (w , " %-9s %s\n " , "Context:" , t .activeContext )
248288 }
249289 fmt .Fprintf (w , " %-9s %s\n " , "Token:" , "stored in OS keychain" )
250290
251- if ctxErr == nil && len ( all ) > 1 {
291+ if t . totalContexts > 1 {
252292 fmt .Fprintln (w )
253- fmt .Fprintf (w , "%d login contexts saved; run 'entire auth contexts' to list or 'entire auth use <name>' to switch.\n " , len ( all ) )
293+ fmt .Fprintf (w , "%d login contexts saved; run 'entire auth contexts' to list or 'entire auth use <name>' to switch.\n " , t . totalContexts )
254294 }
255295 return nil
256296}
0 commit comments