github.com/andrewhsu/cli/v2@v2.0.1-0.20210910131313-d4b4061f5b89/pkg/cmd/auth/status/status.go (about) 1 package status 2 3 import ( 4 "errors" 5 "fmt" 6 "net/http" 7 8 "github.com/MakeNowJust/heredoc" 9 "github.com/andrewhsu/cli/v2/api" 10 "github.com/andrewhsu/cli/v2/internal/config" 11 "github.com/andrewhsu/cli/v2/pkg/cmd/auth/shared" 12 "github.com/andrewhsu/cli/v2/pkg/cmdutil" 13 "github.com/andrewhsu/cli/v2/pkg/iostreams" 14 "github.com/spf13/cobra" 15 ) 16 17 type StatusOptions struct { 18 HttpClient func() (*http.Client, error) 19 IO *iostreams.IOStreams 20 Config func() (config.Config, error) 21 22 Hostname string 23 ShowToken bool 24 } 25 26 func NewCmdStatus(f *cmdutil.Factory, runF func(*StatusOptions) error) *cobra.Command { 27 opts := &StatusOptions{ 28 HttpClient: f.HttpClient, 29 IO: f.IOStreams, 30 Config: f.Config, 31 } 32 33 cmd := &cobra.Command{ 34 Use: "status", 35 Args: cobra.ExactArgs(0), 36 Short: "View authentication status", 37 Long: heredoc.Doc(`Verifies and displays information about your authentication state. 38 39 This command will test your authentication state for each GitHub host that gh knows about and 40 report on any issues. 41 `), 42 RunE: func(cmd *cobra.Command, args []string) error { 43 if runF != nil { 44 return runF(opts) 45 } 46 47 return statusRun(opts) 48 }, 49 } 50 51 cmd.Flags().StringVarP(&opts.Hostname, "hostname", "h", "", "Check a specific hostname's auth status") 52 cmd.Flags().BoolVarP(&opts.ShowToken, "show-token", "t", false, "Display the auth token") 53 54 return cmd 55 } 56 57 func statusRun(opts *StatusOptions) error { 58 cfg, err := opts.Config() 59 if err != nil { 60 return err 61 } 62 63 // TODO check tty 64 65 stderr := opts.IO.ErrOut 66 67 cs := opts.IO.ColorScheme() 68 69 statusInfo := map[string][]string{} 70 71 hostnames, err := cfg.Hosts() 72 if err != nil { 73 return err 74 } 75 if len(hostnames) == 0 { 76 fmt.Fprintf(stderr, 77 "You are not logged into any GitHub hosts. Run %s to authenticate.\n", cs.Bold("gh auth login")) 78 return cmdutil.SilentError 79 } 80 81 httpClient, err := opts.HttpClient() 82 if err != nil { 83 return err 84 } 85 86 var failed bool 87 var isHostnameFound bool 88 89 for _, hostname := range hostnames { 90 if opts.Hostname != "" && opts.Hostname != hostname { 91 continue 92 } 93 isHostnameFound = true 94 95 token, tokenSource, _ := cfg.GetWithSource(hostname, "oauth_token") 96 tokenIsWriteable := cfg.CheckWriteable(hostname, "oauth_token") == nil 97 98 statusInfo[hostname] = []string{} 99 addMsg := func(x string, ys ...interface{}) { 100 statusInfo[hostname] = append(statusInfo[hostname], fmt.Sprintf(x, ys...)) 101 } 102 103 if err := shared.HasMinimumScopes(httpClient, hostname, token); err != nil { 104 var missingScopes *shared.MissingScopesError 105 if errors.As(err, &missingScopes) { 106 addMsg("%s %s: the token in %s is %s", cs.Red("X"), hostname, tokenSource, err) 107 if tokenIsWriteable { 108 addMsg("- To request missing scopes, run: %s %s\n", 109 cs.Bold("gh auth refresh -h"), 110 cs.Bold(hostname)) 111 } 112 } else { 113 addMsg("%s %s: authentication failed", cs.Red("X"), hostname) 114 addMsg("- The %s token in %s is no longer valid.", cs.Bold(hostname), tokenSource) 115 if tokenIsWriteable { 116 addMsg("- To re-authenticate, run: %s %s", 117 cs.Bold("gh auth login -h"), cs.Bold(hostname)) 118 addMsg("- To forget about this host, run: %s %s", 119 cs.Bold("gh auth logout -h"), cs.Bold(hostname)) 120 } 121 } 122 failed = true 123 } else { 124 apiClient := api.NewClientFromHTTP(httpClient) 125 username, err := api.CurrentLoginName(apiClient, hostname) 126 if err != nil { 127 addMsg("%s %s: api call failed: %s", cs.Red("X"), hostname, err) 128 } 129 addMsg("%s Logged in to %s as %s (%s)", cs.SuccessIcon(), hostname, cs.Bold(username), tokenSource) 130 proto, _ := cfg.Get(hostname, "git_protocol") 131 if proto != "" { 132 addMsg("%s Git operations for %s configured to use %s protocol.", 133 cs.SuccessIcon(), hostname, cs.Bold(proto)) 134 } 135 tokenDisplay := "*******************" 136 if opts.ShowToken { 137 tokenDisplay = token 138 } 139 addMsg("%s Token: %s", cs.SuccessIcon(), tokenDisplay) 140 } 141 addMsg("") 142 143 // NB we could take this opportunity to add or fix the "user" key in the hosts config. I chose 144 // not to since I wanted this command to be read-only. 145 } 146 147 if !isHostnameFound { 148 fmt.Fprintf(stderr, 149 "Hostname %q not found among authenticated GitHub hosts\n", opts.Hostname) 150 return cmdutil.SilentError 151 } 152 153 for _, hostname := range hostnames { 154 lines, ok := statusInfo[hostname] 155 if !ok { 156 continue 157 } 158 fmt.Fprintf(stderr, "%s\n", cs.Bold(hostname)) 159 for _, line := range lines { 160 fmt.Fprintf(stderr, " %s\n", line) 161 } 162 } 163 164 if failed { 165 return cmdutil.SilentError 166 } 167 168 return nil 169 }