Old message:
read-only token in GH_TOKEN cannot be modified
This message was vague and some users did not understand that this
refers to the value that is read from environment variables.
New message:
$ GH_TOKEN=123 ghd auth login -h github.com
The value of the GH_TOKEN environment variable is being used for authentication.
To have GitHub CLI store credentials instead, first clear the value from the environment.
165 lines
3.9 KiB
Go
165 lines
3.9 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/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 opts.Hostname == "" && !opts.IO.CanPrompt() {
|
|
return &cmdutil.FlagError{Err: errors.New("--hostname required when not running interactively")}
|
|
}
|
|
|
|
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 {
|
|
hostname := opts.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 {
|
|
var roErr *config.ReadOnlyEnvError
|
|
if errors.As(err, &roErr) {
|
|
fmt.Fprintf(opts.IO.ErrOut, "The value of the %s environment variable is being used for authentication.\n", roErr.Variable)
|
|
fmt.Fprint(opts.IO.ErrOut, "To erase credentials stored in GitHub CLI, first clear the value from the environment.\n")
|
|
return cmdutil.SilentError
|
|
}
|
|
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 opts.IO.CanPrompt() {
|
|
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)
|
|
}
|
|
|
|
isTTY := opts.IO.IsStdinTTY() && opts.IO.IsStdoutTTY()
|
|
|
|
if isTTY {
|
|
cs := opts.IO.ColorScheme()
|
|
fmt.Fprintf(opts.IO.ErrOut, "%s Logged out of %s%s\n",
|
|
cs.SuccessIcon(), cs.Bold(hostname), usernameStr)
|
|
}
|
|
|
|
return nil
|
|
}
|