github.com/secman-team/gh-api@v1.8.2/pkg/cmd/auth/logout/logout.go (about)

     1  package logout
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"net/http"
     7  
     8  	"github.com/AlecAivazis/survey/v2"
     9  	"github.com/MakeNowJust/heredoc"
    10  	"github.com/secman-team/gh-api/api"
    11  	"github.com/secman-team/gh-api/core/config"
    12  	"github.com/secman-team/gh-api/pkg/cmdutil"
    13  	"github.com/secman-team/gh-api/pkg/iostreams"
    14  	"github.com/secman-team/gh-api/pkg/prompt"
    15  	"github.com/spf13/cobra"
    16  )
    17  
    18  type LogoutOptions struct {
    19  	HttpClient func() (*http.Client, error)
    20  	IO         *iostreams.IOStreams
    21  	Config     func() (config.Config, error)
    22  
    23  	Hostname string
    24  }
    25  
    26  func NewCmdLogout(f *cmdutil.Factory, runF func(*LogoutOptions) error) *cobra.Command {
    27  	opts := &LogoutOptions{
    28  		HttpClient: f.HttpClient,
    29  		IO:         f.IOStreams,
    30  		Config:     f.Config,
    31  	}
    32  
    33  	cmd := &cobra.Command{
    34  		Use:   "logout",
    35  		Args:  cobra.ExactArgs(0),
    36  		Short: "Log out of a GitHub host",
    37  		Long: heredoc.Doc(`Remove authentication for a GitHub host.
    38  
    39  			This command removes the authentication configuration for a host either specified
    40  			interactively or via --hostname.
    41  		`),
    42  		Example: heredoc.Doc(`
    43  			$ gh auth logout
    44  			# => select what host to log out of via a prompt
    45  
    46  			$ gh auth logout --hostname enterprise.internal
    47  			# => log out of specified host
    48  		`),
    49  		RunE: func(cmd *cobra.Command, args []string) error {
    50  			if opts.Hostname == "" && !opts.IO.CanPrompt() {
    51  				return &cmdutil.FlagError{Err: errors.New("--hostname required when not running interactively")}
    52  			}
    53  
    54  			if runF != nil {
    55  				return runF(opts)
    56  			}
    57  
    58  			return logoutRun(opts)
    59  		},
    60  	}
    61  
    62  	cmd.Flags().StringVarP(&opts.Hostname, "hostname", "h", "", "The hostname of the GitHub instance to log out of")
    63  
    64  	return cmd
    65  }
    66  
    67  func logoutRun(opts *LogoutOptions) error {
    68  	hostname := opts.Hostname
    69  
    70  	cfg, err := opts.Config()
    71  	if err != nil {
    72  		return err
    73  	}
    74  
    75  	candidates, err := cfg.Hosts()
    76  	if err != nil {
    77  		return fmt.Errorf("not logged in to any hosts")
    78  	}
    79  
    80  	if hostname == "" {
    81  		if len(candidates) == 1 {
    82  			hostname = candidates[0]
    83  		} else {
    84  			err = prompt.SurveyAskOne(&survey.Select{
    85  				Message: "What account do you want to log out of?",
    86  				Options: candidates,
    87  			}, &hostname)
    88  
    89  			if err != nil {
    90  				return fmt.Errorf("could not prompt: %w", err)
    91  			}
    92  		}
    93  	} else {
    94  		var found bool
    95  		for _, c := range candidates {
    96  			if c == hostname {
    97  				found = true
    98  				break
    99  			}
   100  		}
   101  
   102  		if !found {
   103  			return fmt.Errorf("not logged into %s", hostname)
   104  		}
   105  	}
   106  
   107  	if err := cfg.CheckWriteable(hostname, "oauth_token"); err != nil {
   108  		var roErr *config.ReadOnlyEnvError
   109  		if errors.As(err, &roErr) {
   110  			fmt.Fprintf(opts.IO.ErrOut, "The value of the %s environment variable is being used for authentication.\n", roErr.Variable)
   111  			fmt.Fprint(opts.IO.ErrOut, "To erase credentials stored in GitHub CLI, first clear the value from the environment.\n")
   112  			return cmdutil.SilentError
   113  		}
   114  		return err
   115  	}
   116  
   117  	httpClient, err := opts.HttpClient()
   118  	if err != nil {
   119  		return err
   120  	}
   121  	apiClient := api.NewClientFromHTTP(httpClient)
   122  
   123  	username, err := api.CurrentLoginName(apiClient, hostname)
   124  	if err != nil {
   125  		// suppressing; the user is trying to delete this token and it might be bad.
   126  		// we'll see if the username is in the config and fall back to that.
   127  		username, _ = cfg.Get(hostname, "user")
   128  	}
   129  
   130  	usernameStr := ""
   131  	if username != "" {
   132  		usernameStr = fmt.Sprintf(" account '%s'", username)
   133  	}
   134  
   135  	if opts.IO.CanPrompt() {
   136  		var keepGoing bool
   137  		err := prompt.SurveyAskOne(&survey.Confirm{
   138  			Message: fmt.Sprintf("Are you sure you want to log out of %s%s?", hostname, usernameStr),
   139  			Default: true,
   140  		}, &keepGoing)
   141  		if err != nil {
   142  			return fmt.Errorf("could not prompt: %w", err)
   143  		}
   144  
   145  		if !keepGoing {
   146  			return nil
   147  		}
   148  	}
   149  
   150  	cfg.UnsetHost(hostname)
   151  	err = cfg.Write()
   152  	if err != nil {
   153  		return fmt.Errorf("failed to write config, authentication configuration not updated: %w", err)
   154  	}
   155  
   156  	isTTY := opts.IO.IsStdinTTY() && opts.IO.IsStdoutTTY()
   157  
   158  	if isTTY {
   159  		cs := opts.IO.ColorScheme()
   160  		fmt.Fprintf(opts.IO.ErrOut, "%s Logged out of %s%s\n",
   161  			cs.SuccessIcon(), cs.Bold(hostname), usernameStr)
   162  	}
   163  
   164  	return nil
   165  }