1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177
|
package authswitch
import (
"errors"
"fmt"
"slices"
"github.com/MakeNowJust/heredoc"
"github.com/cli/cli/v2/internal/config"
"github.com/cli/cli/v2/pkg/cmd/auth/shared"
"github.com/cli/cli/v2/pkg/cmdutil"
"github.com/cli/cli/v2/pkg/iostreams"
"github.com/spf13/cobra"
)
type SwitchOptions struct {
IO *iostreams.IOStreams
Config func() (config.Config, error)
Prompter shared.Prompt
Hostname string
Username string
}
func NewCmdSwitch(f *cmdutil.Factory, runF func(*SwitchOptions) error) *cobra.Command {
opts := SwitchOptions{
IO: f.IOStreams,
Config: f.Config,
Prompter: f.Prompter,
}
cmd := &cobra.Command{
Use: "switch",
Args: cobra.ExactArgs(0),
Short: "Switch active GitHub account",
Long: heredoc.Docf(`
Switch the active account for a GitHub host.
This command changes the authentication configuration that will
be used when running commands targeting the specified GitHub host.
If the specified host has two accounts, the active account will be switched
automatically. If there are more than two accounts, disambiguation will be
required either through the %[1]s--user%[1]s flag or an interactive prompt.
For a list of authenticated accounts you can run %[1]sgh auth status%[1]s.
`, "`"),
Example: heredoc.Doc(`
# Select what host and account to switch to via a prompt
$ gh auth switch
# Switch to a specific host and specific account
$ gh auth logout --hostname enterprise.internal --user monalisa
`),
RunE: func(c *cobra.Command, args []string) error {
if runF != nil {
return runF(&opts)
}
return switchRun(&opts)
},
}
cmd.Flags().StringVarP(&opts.Hostname, "hostname", "h", "", "The hostname of the GitHub instance to switch account for")
cmd.Flags().StringVarP(&opts.Username, "user", "u", "", "The account to switch to")
return cmd
}
type hostUser struct {
host string
user string
active bool
}
type candidates []hostUser
func switchRun(opts *SwitchOptions) error {
hostname := opts.Hostname
username := opts.Username
cfg, err := opts.Config()
if err != nil {
return err
}
authCfg := cfg.Authentication()
knownHosts := authCfg.Hosts()
if len(knownHosts) == 0 {
return fmt.Errorf("not logged in to any hosts")
}
if hostname != "" {
if !slices.Contains(knownHosts, hostname) {
return fmt.Errorf("not logged in to %s", hostname)
}
if username != "" {
knownUsers := cfg.Authentication().UsersForHost(hostname)
if !slices.Contains(knownUsers, username) {
return fmt.Errorf("not logged in to %s account %s", hostname, username)
}
}
}
var candidates candidates
for _, host := range knownHosts {
if hostname != "" && host != hostname {
continue
}
hostActiveUser, err := authCfg.ActiveUser(host)
if err != nil {
return err
}
knownUsers := cfg.Authentication().UsersForHost(host)
for _, user := range knownUsers {
if username != "" && user != username {
continue
}
candidates = append(candidates, hostUser{host: host, user: user, active: user == hostActiveUser})
}
}
if len(candidates) == 0 {
return errors.New("no accounts matched that criteria")
} else if len(candidates) == 1 {
hostname = candidates[0].host
username = candidates[0].user
} else if len(candidates) == 2 &&
candidates[0].host == candidates[1].host {
// If there is a single host with two users, automatically switch to the
// inactive user without prompting.
hostname = candidates[0].host
username = candidates[0].user
if candidates[0].active {
username = candidates[1].user
}
} else if !opts.IO.CanPrompt() {
return errors.New("unable to determine which account to switch to, please specify `--hostname` and `--user`")
} else {
prompts := make([]string, len(candidates))
for i, c := range candidates {
prompt := fmt.Sprintf("%s (%s)", c.user, c.host)
if c.active {
prompt += " - active"
}
prompts[i] = prompt
}
selected, err := opts.Prompter.Select(
"What account do you want to switch to?", "", prompts)
if err != nil {
return fmt.Errorf("could not prompt: %w", err)
}
hostname = candidates[selected].host
username = candidates[selected].user
}
if src, writeable := shared.AuthTokenWriteable(authCfg, hostname); !writeable {
fmt.Fprintf(opts.IO.ErrOut, "The value of the %s environment variable is being used for authentication.\n", src)
fmt.Fprint(opts.IO.ErrOut, "To have GitHub CLI manage credentials instead, first clear the value from the environment.\n")
return cmdutil.SilentError
}
cs := opts.IO.ColorScheme()
if err := authCfg.SwitchUser(hostname, username); err != nil {
fmt.Fprintf(opts.IO.ErrOut, "%s Failed to switch account for %s to %s\n",
cs.FailureIcon(), hostname, cs.Bold(username))
return err
}
fmt.Fprintf(opts.IO.ErrOut, "%s Switched active account for %s to %s\n",
cs.SuccessIcon(), hostname, cs.Bold(username))
return nil
}
|