github.com/cli/cli@v1.14.1-0.20210902173923-1af6a669e342/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/cli/cli/api" 11 "github.com/cli/cli/internal/config" 12 "github.com/cli/cli/pkg/cmdutil" 13 "github.com/cli/cli/pkg/iostreams" 14 "github.com/cli/cli/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 err 78 } 79 if len(candidates) == 0 { 80 return fmt.Errorf("not logged in to any hosts") 81 } 82 83 if hostname == "" { 84 if len(candidates) == 1 { 85 hostname = candidates[0] 86 } else { 87 err = prompt.SurveyAskOne(&survey.Select{ 88 Message: "What account do you want to log out of?", 89 Options: candidates, 90 }, &hostname) 91 92 if err != nil { 93 return fmt.Errorf("could not prompt: %w", err) 94 } 95 } 96 } else { 97 var found bool 98 for _, c := range candidates { 99 if c == hostname { 100 found = true 101 break 102 } 103 } 104 105 if !found { 106 return fmt.Errorf("not logged into %s", hostname) 107 } 108 } 109 110 if err := cfg.CheckWriteable(hostname, "oauth_token"); err != nil { 111 var roErr *config.ReadOnlyEnvError 112 if errors.As(err, &roErr) { 113 fmt.Fprintf(opts.IO.ErrOut, "The value of the %s environment variable is being used for authentication.\n", roErr.Variable) 114 fmt.Fprint(opts.IO.ErrOut, "To erase credentials stored in GitHub CLI, first clear the value from the environment.\n") 115 return cmdutil.SilentError 116 } 117 return err 118 } 119 120 httpClient, err := opts.HttpClient() 121 if err != nil { 122 return err 123 } 124 apiClient := api.NewClientFromHTTP(httpClient) 125 126 username, err := api.CurrentLoginName(apiClient, hostname) 127 if err != nil { 128 // suppressing; the user is trying to delete this token and it might be bad. 129 // we'll see if the username is in the config and fall back to that. 130 username, _ = cfg.Get(hostname, "user") 131 } 132 133 usernameStr := "" 134 if username != "" { 135 usernameStr = fmt.Sprintf(" account '%s'", username) 136 } 137 138 if opts.IO.CanPrompt() { 139 var keepGoing bool 140 err := prompt.SurveyAskOne(&survey.Confirm{ 141 Message: fmt.Sprintf("Are you sure you want to log out of %s%s?", hostname, usernameStr), 142 Default: true, 143 }, &keepGoing) 144 if err != nil { 145 return fmt.Errorf("could not prompt: %w", err) 146 } 147 148 if !keepGoing { 149 return nil 150 } 151 } 152 153 cfg.UnsetHost(hostname) 154 err = cfg.Write() 155 if err != nil { 156 return fmt.Errorf("failed to write config, authentication configuration not updated: %w", err) 157 } 158 159 isTTY := opts.IO.IsStdinTTY() && opts.IO.IsStdoutTTY() 160 161 if isTTY { 162 cs := opts.IO.ColorScheme() 163 fmt.Fprintf(opts.IO.ErrOut, "%s Logged out of %s%s\n", 164 cs.SuccessIcon(), cs.Bold(hostname), usernameStr) 165 } 166 167 return nil 168 }