@@ -19,40 +19,56 @@ import (
1919 "github.com/spf13/cobra"
2020)
2121
22+ type authEntryState string
23+
24+ const (
25+ authEntryStateSuccess = "success"
26+ authEntryStateTimeout = "timeout"
27+ authEntryStateError = "error"
28+ )
29+
2230type authEntry struct {
23- State authState `json:"state"`
24- Error string `json:"error"`
25- Active bool `json:"active"`
26- Host string `json:"host"`
27- Login string `json:"login"`
28- TokenSource string `json:"tokenSource"`
29- Token string `json:"token"`
30- Scopes string `json:"scopes"`
31- GitProtocol string `json:"gitProtocol"`
31+ State authEntryState `json:"state"`
32+ Error string `json:"error,omitempty"`
33+ Active bool `json:"active"`
34+ Host string `json:"host"`
35+ Login string `json:"login"`
36+ TokenSource string `json:"tokenSource"`
37+ Token string `json:"token,omitempty"`
38+ Scopes string `json:"scopes,omitempty"`
39+ GitProtocol string `json:"gitProtocol"`
40+ }
41+
42+ type authStatus struct {
43+ Hosts map [string ][]authEntry `json:"hosts"`
44+ }
45+
46+ func newAuthStatus () * authStatus {
47+ return & authStatus {
48+ Hosts : make (map [string ][]authEntry ),
49+ }
50+ }
51+
52+ var authStatusFields = []string {
53+ "hosts" ,
3254}
3355
34- var authFields = []string {
35- "state" ,
36- "error" ,
37- "active" ,
38- "host" ,
39- "login" ,
40- "tokenSource" ,
41- "token" ,
42- "scopes" ,
43- "gitProtocol" ,
56+ func (a authStatus ) ExportData (fields []string ) map [string ]interface {} {
57+ return cmdutil .StructExportData (a , fields )
4458}
4559
46- func (e authEntry ) String (cs * iostreams.ColorScheme , showToken bool ) string {
60+ func (e authEntry ) String (cs * iostreams.ColorScheme ) string {
4761 var sb strings.Builder
48- if e .State == authStateSuccess {
62+
63+ switch e .State {
64+ case authEntryStateSuccess :
4965 sb .WriteString (
5066 fmt .Sprintf (" %s Logged in to %s account %s (%s)\n " , cs .SuccessIcon (), e .Host , cs .Bold (e .Login ), e .TokenSource ),
5167 )
5268 activeStr := fmt .Sprintf ("%v" , e .Active )
5369 sb .WriteString (fmt .Sprintf (" - Active account: %s\n " , cs .Bold (activeStr )))
5470 sb .WriteString (fmt .Sprintf (" - Git operations protocol: %s\n " , cs .Bold (e .GitProtocol )))
55- sb .WriteString (fmt .Sprintf (" - Token: %s\n " , cs .Bold (displayToken ( e .Token , showToken ) )))
71+ sb .WriteString (fmt .Sprintf (" - Token: %s\n " , cs .Bold (e .Token )))
5672
5773 if expectScopes (e .Token ) {
5874 sb .WriteString (fmt .Sprintf (" - Token scopes: %s\n " , cs .Bold (displayScopes (e .Scopes ))))
@@ -68,31 +84,34 @@ func (e authEntry) String(cs *iostreams.ColorScheme, showToken bool) string {
6884 }
6985 }
7086 }
71- return sb .String ()
72- }
7387
74- if e .Login != "" {
75- sb .WriteString (fmt .Sprintf (" %s %s to %s account %s (%s)\n " , cs .Red ("X" ), e .Error , e .Host , cs .Bold (e .Login ), e .TokenSource ))
76- } else {
77- sb .WriteString (fmt .Sprintf (" %s %s to %s using token (%s)\n " , cs .Red ("X" ), e .Error , e .Host , e .TokenSource ))
78- }
79- activeStr := fmt .Sprintf ("%v" , e .Active )
80- sb .WriteString (fmt .Sprintf (" - Active account: %s\n " , cs .Bold (activeStr )))
81-
82- if e .State == authStateError {
88+ case authEntryStateError :
89+ if e .Login != "" {
90+ sb .WriteString (fmt .Sprintf (" %s Failed to log in to %s account %s (%s)\n " , cs .Red ("X" ), e .Host , cs .Bold (e .Login ), e .TokenSource ))
91+ } else {
92+ sb .WriteString (fmt .Sprintf (" %s Failed to log in to %s using token (%s)\n " , cs .Red ("X" ), e .Host , e .TokenSource ))
93+ }
94+ activeStr := fmt .Sprintf ("%v" , e .Active )
95+ sb .WriteString (fmt .Sprintf (" - Active account: %s\n " , cs .Bold (activeStr )))
8396 sb .WriteString (fmt .Sprintf (" - The token in %s is invalid.\n " , e .TokenSource ))
8497 if authTokenWriteable (e .TokenSource ) {
8598 loginInstructions := fmt .Sprintf ("gh auth login -h %s" , e .Host )
8699 logoutInstructions := fmt .Sprintf ("gh auth logout -h %s -u %s" , e .Host , e .Login )
87100 sb .WriteString (fmt .Sprintf (" - To re-authenticate, run: %s\n " , cs .Bold (loginInstructions )))
88101 sb .WriteString (fmt .Sprintf (" - To forget about this account, run: %s\n " , cs .Bold (logoutInstructions )))
89102 }
103+
104+ case authEntryStateTimeout :
105+ if e .Login != "" {
106+ sb .WriteString (fmt .Sprintf (" %s Timeout trying to log in to %s account %s (%s)\n " , cs .Red ("X" ), e .Host , cs .Bold (e .Login ), e .TokenSource ))
107+ } else {
108+ sb .WriteString (fmt .Sprintf (" %s Timeout trying to log in to %s using token (%s)\n " , cs .Red ("X" ), e .Host , e .TokenSource ))
109+ }
110+ activeStr := fmt .Sprintf ("%v" , e .Active )
111+ sb .WriteString (fmt .Sprintf (" - Active account: %s\n " , cs .Bold (activeStr )))
90112 }
91- return sb .String ()
92- }
93113
94- func (e authEntry ) ExportData (fields []string ) map [string ]interface {} {
95- return cmdutil .StructExportData (e , fields )
114+ return sb .String ()
96115}
97116
98117type StatusOptions struct {
@@ -138,22 +157,15 @@ func NewCmdStatus(f *cmdutil.Factory, runF func(*StatusOptions) error) *cobra.Co
138157 $ gh auth status --show-token
139158
140159 # Format authentication status as JSON
141- $ gh auth status --json active,login,host
160+ $ gh auth status --json hosts
142161
143162 # Include plain text token in JSON output
144- $ gh auth status --json token,login
163+ $ gh auth status --json hosts --show- token
145164
146- # Format output as a flat JSON array
147- $ gh auth status --json active,host,login --jq add
165+ # Format hosts as a flat JSON array
166+ $ gh auth status --json hosts --jq '.hosts | add'
148167 ` ),
149168 RunE : func (cmd * cobra.Command , args []string ) error {
150- if err := cmdutil .MutuallyExclusive (
151- "`--json` and `--show-token` cannot be used together. To include the token in the JSON output, use `--json token`." ,
152- opts .Exporter != nil ,
153- opts .ShowToken ,
154- ); err != nil {
155- return err
156- }
157169 if runF != nil {
158170 return runF (opts )
159171 }
@@ -167,7 +179,7 @@ func NewCmdStatus(f *cmdutil.Factory, runF func(*StatusOptions) error) *cobra.Co
167179 cmd .Flags ().BoolVarP (& opts .Active , "active" , "a" , false , "Display the active account only" )
168180
169181 // the json flags are intentionally not given a shorthand to avoid conflict with -t/--show-token
170- cmdutil .AddJSONFlagsWithoutShorthand (cmd , & opts .Exporter , authFields )
182+ cmdutil .AddJSONFlagsWithoutShorthand (cmd , & opts .Exporter , authStatusFields )
171183
172184 return cmd
173185}
@@ -183,20 +195,13 @@ func statusRun(opts *StatusOptions) error {
183195 stdout := opts .IO .Out
184196 cs := opts .IO .ColorScheme ()
185197
186- showToken := opts .ShowToken
187- if opts .Exporter != nil && slices .Contains (opts .Exporter .Fields (), "token" ) {
188- showToken = true
189- }
190-
191- statuses := make (map [string ][]authEntry )
192-
193198 hostnames := authCfg .Hosts ()
194199 if len (hostnames ) == 0 {
195200 fmt .Fprintf (stderr ,
196201 "You are not logged into any GitHub hosts. To log in, run: %s\n " , cs .Bold ("gh auth login" ))
197202 if opts .Exporter != nil {
198203 // In machine-friendly mode, we always exit with no error.
199- opts .Exporter .Write (opts .IO , struct {}{} )
204+ opts .Exporter .Write (opts .IO , newAuthStatus () )
200205 return nil
201206 }
202207 return cmdutil .SilentError
@@ -207,7 +212,7 @@ func statusRun(opts *StatusOptions) error {
207212 "You are not logged into any accounts on %s\n " , opts .Hostname )
208213 if opts .Exporter != nil {
209214 // In machine-friendly mode, we always exit with no error.
210- opts .Exporter .Write (opts .IO , struct {}{} )
215+ opts .Exporter .Write (opts .IO , newAuthStatus () )
211216 return nil
212217 }
213218 return cmdutil .SilentError
@@ -218,6 +223,9 @@ func statusRun(opts *StatusOptions) error {
218223 return err
219224 }
220225
226+ var finalErr error
227+ statuses := newAuthStatus ()
228+
221229 for _ , hostname := range hostnames {
222230 if opts .Hostname != "" && opts .Hostname != hostname {
223231 continue
@@ -233,15 +241,14 @@ func statusRun(opts *StatusOptions) error {
233241 active : true ,
234242 gitProtocol : gitProtocol ,
235243 hostname : hostname ,
236- showToken : showToken ,
237244 token : activeUserToken ,
238245 tokenSource : activeUserTokenSource ,
239246 username : activeUser ,
240247 })
241- statuses [hostname ] = append (statuses [hostname ], entry )
248+ statuses . Hosts [hostname ] = append (statuses . Hosts [hostname ], entry )
242249
243- if err == nil && ! isValidEntry ( entry ) {
244- err = cmdutil .SilentError
250+ if finalErr == nil && entry . State != authEntryStateSuccess {
251+ finalErr = cmdutil .SilentError
245252 }
246253
247254 if opts .Active {
@@ -258,33 +265,46 @@ func statusRun(opts *StatusOptions) error {
258265 active : false ,
259266 gitProtocol : gitProtocol ,
260267 hostname : hostname ,
261- showToken : showToken ,
262268 token : token ,
263269 tokenSource : tokenSource ,
264270 username : username ,
265271 })
266- statuses [hostname ] = append (statuses [hostname ], entry )
272+ statuses .Hosts [hostname ] = append (statuses .Hosts [hostname ], entry )
273+
274+ if finalErr == nil && entry .State != authEntryStateSuccess {
275+ finalErr = cmdutil .SilentError
276+ }
277+ }
278+ }
267279
268- if err == nil && ! isValidEntry (entry ) {
269- err = cmdutil .SilentError
280+ if ! opts .ShowToken {
281+ for _ , host := range statuses .Hosts {
282+ for i := range host {
283+ if opts .Exporter != nil {
284+ // In machine-readable we just drop the token
285+ host [i ].Token = ""
286+ } else {
287+ host [i ].Token = maskToken (host [i ].Token )
288+ }
270289 }
271290 }
272291 }
273292
274293 if opts .Exporter != nil {
294+ // In machine-friendly mode, we always exit with no error.
275295 opts .Exporter .Write (opts .IO , statuses )
276296 return nil
277297 }
278298
279299 prevEntry := false
280300 for _ , hostname := range hostnames {
281- entries , ok := statuses [hostname ]
301+ entries , ok := statuses . Hosts [hostname ]
282302 if ! ok {
283303 continue
284304 }
285305
286306 stream := stdout
287- if err != nil {
307+ if finalErr != nil {
288308 stream = stderr
289309 }
290310
@@ -294,26 +314,21 @@ func statusRun(opts *StatusOptions) error {
294314 prevEntry = true
295315 fmt .Fprintf (stream , "%s\n " , cs .Bold (hostname ))
296316 for i , entry := range entries {
297- fmt .Fprintf (stream , "%s" , entry .String (cs , showToken ))
317+ fmt .Fprintf (stream , "%s" , entry .String (cs ))
298318 if i < len (entries )- 1 {
299319 fmt .Fprint (stream , "\n " )
300320 }
301321 }
302322 }
303323
304- return err
324+ return finalErr
305325}
306326
307- func displayToken (token string , printRaw bool ) string {
308- if printRaw {
309- return token
310- }
311-
327+ func maskToken (token string ) string {
312328 if idx := strings .LastIndexByte (token , '_' ); idx > - 1 {
313329 prefix := token [0 : idx + 1 ]
314330 return prefix + strings .Repeat ("*" , len (token )- len (prefix ))
315331 }
316-
317332 return strings .Repeat ("*" , len (token ))
318333}
319334
@@ -336,7 +351,6 @@ type buildEntryOptions struct {
336351 active bool
337352 gitProtocol string
338353 hostname string
339- showToken bool
340354 token string
341355 tokenSource string
342356 username string
@@ -367,8 +381,8 @@ func buildEntry(httpClient *http.Client, opts buildEntryOptions) authEntry {
367381 var err error
368382 entry .Login , err = api .CurrentLoginName (apiClient , opts .hostname )
369383 if err != nil {
370- entry .State = authStateError
371- entry .Error = "Failed to log in"
384+ entry .State = authEntryStateError
385+ entry .Error = err . Error ()
372386 return entry
373387 }
374388 }
@@ -378,25 +392,21 @@ func buildEntry(httpClient *http.Client, opts buildEntryOptions) authEntry {
378392 if err != nil {
379393 var networkError net.Error
380394 if errors .As (err , & networkError ) && networkError .Timeout () {
381- entry .State = authStateTimeout
382- entry .Error = "Timeout trying to log in"
395+ entry .State = authEntryStateTimeout
396+ entry .Error = err . Error ()
383397 return entry
384398 }
385399
386- entry .State = authStateError
387- entry .Error = "Failed to log in"
400+ entry .State = authEntryStateError
401+ entry .Error = err . Error ()
388402 return entry
389403 }
390404 entry .Scopes = scopesHeader
391405
392- entry .State = authStateSuccess
406+ entry .State = authEntryStateSuccess
393407 return entry
394408}
395409
396410func authTokenWriteable (src string ) bool {
397411 return ! strings .HasSuffix (src , "_TOKEN" )
398412}
399-
400- func isValidEntry (entry authEntry ) bool {
401- return entry .State == authStateSuccess
402- }
0 commit comments