github.com/secman-team/gh-api@v1.8.2/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/secman-team/gh-api/api"
    10  	"github.com/secman-team/gh-api/core/config"
    11  	"github.com/secman-team/gh-api/pkg/cmd/auth/shared"
    12  	"github.com/secman-team/gh-api/pkg/cmdutil"
    13  	"github.com/secman-team/gh-api/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 len(hostnames) == 0 || err != nil {
    73  		fmt.Fprintf(stderr,
    74  			"You are not logged into any GitHub hosts. Run %s to authenticate.\n", cs.Bold("gh auth login"))
    75  		return cmdutil.SilentError
    76  	}
    77  
    78  	httpClient, err := opts.HttpClient()
    79  	if err != nil {
    80  		return err
    81  	}
    82  
    83  	var failed bool
    84  	var isHostnameFound bool
    85  
    86  	for _, hostname := range hostnames {
    87  		if opts.Hostname != "" && opts.Hostname != hostname {
    88  			continue
    89  		}
    90  		isHostnameFound = true
    91  
    92  		token, tokenSource, _ := cfg.GetWithSource(hostname, "oauth_token")
    93  		tokenIsWriteable := cfg.CheckWriteable(hostname, "oauth_token") == nil
    94  
    95  		statusInfo[hostname] = []string{}
    96  		addMsg := func(x string, ys ...interface{}) {
    97  			statusInfo[hostname] = append(statusInfo[hostname], fmt.Sprintf(x, ys...))
    98  		}
    99  
   100  		if err := shared.HasMinimumScopes(httpClient, hostname, token); err != nil {
   101  			var missingScopes *shared.MissingScopesError
   102  			if errors.As(err, &missingScopes) {
   103  				addMsg("%s %s: the token in %s is %s", cs.Red("X"), hostname, tokenSource, err)
   104  				if tokenIsWriteable {
   105  					addMsg("- To request missing scopes, run: %s %s\n",
   106  						cs.Bold("gh auth refresh -h"),
   107  						cs.Bold(hostname))
   108  				}
   109  			} else {
   110  				addMsg("%s %s: authentication failed", cs.Red("X"), hostname)
   111  				addMsg("- The %s token in %s is no longer valid.", cs.Bold(hostname), tokenSource)
   112  				if tokenIsWriteable {
   113  					addMsg("- To re-authenticate, run: %s %s",
   114  						cs.Bold("gh auth login -h"), cs.Bold(hostname))
   115  					addMsg("- To forget about this host, run: %s %s",
   116  						cs.Bold("gh auth logout -h"), cs.Bold(hostname))
   117  				}
   118  			}
   119  			failed = true
   120  		} else {
   121  			apiClient := api.NewClientFromHTTP(httpClient)
   122  			username, err := api.CurrentLoginName(apiClient, hostname)
   123  			if err != nil {
   124  				addMsg("%s %s: api call failed: %s", cs.Red("X"), hostname, err)
   125  			}
   126  			addMsg("%s Logged in to %s as %s (%s)", cs.SuccessIcon(), hostname, cs.Bold(username), tokenSource)
   127  			proto, _ := cfg.Get(hostname, "git_protocol")
   128  			if proto != "" {
   129  				addMsg("%s Git operations for %s configured to use %s protocol.",
   130  					cs.SuccessIcon(), hostname, cs.Bold(proto))
   131  			}
   132  			tokenDisplay := "*******************"
   133  			if opts.ShowToken {
   134  				tokenDisplay = token
   135  			}
   136  			addMsg("%s Token: %s", cs.SuccessIcon(), tokenDisplay)
   137  		}
   138  		addMsg("")
   139  
   140  		// NB we could take this opportunity to add or fix the "user" key in the hosts config. I chose
   141  		// not to since I wanted this command to be read-only.
   142  	}
   143  
   144  	if !isHostnameFound {
   145  		fmt.Fprintf(stderr,
   146  			"Hostname %q not found among authenticated GitHub hosts\n", opts.Hostname)
   147  		return cmdutil.SilentError
   148  	}
   149  
   150  	for _, hostname := range hostnames {
   151  		lines, ok := statusInfo[hostname]
   152  		if !ok {
   153  			continue
   154  		}
   155  		fmt.Fprintf(stderr, "%s\n", cs.Bold(hostname))
   156  		for _, line := range lines {
   157  			fmt.Fprintf(stderr, "  %s\n", line)
   158  		}
   159  	}
   160  
   161  	if failed {
   162  		return cmdutil.SilentError
   163  	}
   164  
   165  	return nil
   166  }