cli/pkg/cmd/auth/logout/logout.go
Mislav Marohnić c80292c2e8 Extend Config object with GITHUB_TOKEN support
Adding GITHUB_TOKEN & GITHUB_ENTERPRISE_TOKEN support orthogonal to
Config was getting out of hand, especially in `auth` commands that
adjust their messaging and error status based on the presence of these
environment variables.

The new approach builds in support for tokens from environment straight
into Config object by composition. Thus, commands need not ever be
concerned with any specific environment variables.
2020-09-07 21:33:26 +02:00

161 lines
3.5 KiB
Go

package logout
import (
"errors"
"fmt"
"net/http"
"github.com/AlecAivazis/survey/v2"
"github.com/MakeNowJust/heredoc"
"github.com/cli/cli/api"
"github.com/cli/cli/internal/config"
"github.com/cli/cli/pkg/cmdutil"
"github.com/cli/cli/pkg/iostreams"
"github.com/cli/cli/pkg/prompt"
"github.com/cli/cli/utils"
"github.com/spf13/cobra"
)
type LogoutOptions struct {
HttpClient func() (*http.Client, error)
IO *iostreams.IOStreams
Config func() (config.Config, error)
Hostname string
}
func NewCmdLogout(f *cmdutil.Factory, runF func(*LogoutOptions) error) *cobra.Command {
opts := &LogoutOptions{
HttpClient: f.HttpClient,
IO: f.IOStreams,
Config: f.Config,
}
cmd := &cobra.Command{
Use: "logout",
Args: cobra.ExactArgs(0),
Short: "Log out of a GitHub host",
Long: heredoc.Doc(`Remove authentication for a GitHub host.
This command removes the authentication configuration for a host either specified
interactively or via --hostname.
`),
Example: heredoc.Doc(`
$ gh auth logout
# => select what host to log out of via a prompt
$ gh auth logout --hostname enterprise.internal
# => log out of specified host
`),
RunE: func(cmd *cobra.Command, args []string) error {
if runF != nil {
return runF(opts)
}
return logoutRun(opts)
},
}
cmd.Flags().StringVarP(&opts.Hostname, "hostname", "h", "", "The hostname of the GitHub instance to log out of")
return cmd
}
func logoutRun(opts *LogoutOptions) error {
isTTY := opts.IO.IsStdinTTY() && opts.IO.IsStdoutTTY()
hostname := opts.Hostname
if !isTTY && hostname == "" {
return errors.New("--hostname required when not attached to a terminal")
}
showConfirm := isTTY && hostname == ""
cfg, err := opts.Config()
if err != nil {
return err
}
candidates, err := cfg.Hosts()
if err != nil {
return fmt.Errorf("not logged in to any hosts")
}
if hostname == "" {
if len(candidates) == 1 {
hostname = candidates[0]
} else {
err = prompt.SurveyAskOne(&survey.Select{
Message: "What account do you want to log out of?",
Options: candidates,
}, &hostname)
if err != nil {
return fmt.Errorf("could not prompt: %w", err)
}
}
} else {
var found bool
for _, c := range candidates {
if c == hostname {
found = true
break
}
}
if !found {
return fmt.Errorf("not logged into %s", hostname)
}
}
if err := cfg.CheckWriteable(hostname, "oauth_token"); err != nil {
return err
}
httpClient, err := opts.HttpClient()
if err != nil {
return err
}
apiClient := api.NewClientFromHTTP(httpClient)
username, err := api.CurrentLoginName(apiClient, hostname)
if err != nil {
// suppressing; the user is trying to delete this token and it might be bad.
// we'll see if the username is in the config and fall back to that.
username, _ = cfg.Get(hostname, "user")
}
usernameStr := ""
if username != "" {
usernameStr = fmt.Sprintf(" account '%s'", username)
}
if showConfirm {
var keepGoing bool
err := prompt.SurveyAskOne(&survey.Confirm{
Message: fmt.Sprintf("Are you sure you want to log out of %s%s?", hostname, usernameStr),
Default: true,
}, &keepGoing)
if err != nil {
return fmt.Errorf("could not prompt: %w", err)
}
if !keepGoing {
return nil
}
}
cfg.UnsetHost(hostname)
err = cfg.Write()
if err != nil {
return fmt.Errorf("failed to write config, authentication configuration not updated: %w", err)
}
if isTTY {
fmt.Fprintf(opts.IO.ErrOut, "%s Logged out of %s%s\n",
utils.GreenCheck(), utils.Bold(hostname), usernameStr)
}
return nil
}