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  }