github.com/ungtb10d/cli/v2@v2.0.0-20221110210412-98537dd9d6a1/pkg/cmd/auth/refresh/refresh.go (about) 1 package refresh 2 3 import ( 4 "fmt" 5 "net/http" 6 "strings" 7 8 "github.com/MakeNowJust/heredoc" 9 "github.com/ungtb10d/cli/v2/git" 10 "github.com/ungtb10d/cli/v2/internal/authflow" 11 "github.com/ungtb10d/cli/v2/internal/config" 12 "github.com/ungtb10d/cli/v2/pkg/cmd/auth/shared" 13 "github.com/ungtb10d/cli/v2/pkg/cmdutil" 14 "github.com/ungtb10d/cli/v2/pkg/iostreams" 15 "github.com/spf13/cobra" 16 ) 17 18 type RefreshOptions struct { 19 IO *iostreams.IOStreams 20 Config func() (config.Config, error) 21 HttpClient *http.Client 22 GitClient *git.Client 23 Prompter shared.Prompt 24 25 MainExecutable string 26 27 Hostname string 28 Scopes []string 29 AuthFlow func(config.Config, *iostreams.IOStreams, string, []string, bool) error 30 31 Interactive bool 32 } 33 34 func NewCmdRefresh(f *cmdutil.Factory, runF func(*RefreshOptions) error) *cobra.Command { 35 opts := &RefreshOptions{ 36 IO: f.IOStreams, 37 Config: f.Config, 38 AuthFlow: func(cfg config.Config, io *iostreams.IOStreams, hostname string, scopes []string, interactive bool) error { 39 _, err := authflow.AuthFlowWithConfig(cfg, io, hostname, "", scopes, interactive) 40 return err 41 }, 42 HttpClient: &http.Client{}, 43 GitClient: f.GitClient, 44 Prompter: f.Prompter, 45 } 46 47 cmd := &cobra.Command{ 48 Use: "refresh", 49 Args: cobra.ExactArgs(0), 50 Short: "Refresh stored authentication credentials", 51 Long: heredoc.Doc(`Expand or fix the permission scopes for stored credentials. 52 53 The --scopes flag accepts a comma separated list of scopes you want your gh credentials to have. If 54 absent, this command ensures that gh has access to a minimum set of scopes. 55 `), 56 Example: heredoc.Doc(` 57 $ gh auth refresh --scopes write:org,read:public_key 58 # => open a browser to add write:org and read:public_key scopes for use with gh api 59 60 $ gh auth refresh 61 # => open a browser to ensure your authentication credentials have the correct minimum scopes 62 `), 63 RunE: func(cmd *cobra.Command, args []string) error { 64 opts.Interactive = opts.IO.CanPrompt() 65 66 if !opts.Interactive && opts.Hostname == "" { 67 return cmdutil.FlagErrorf("--hostname required when not running interactively") 68 } 69 70 opts.MainExecutable = f.Executable() 71 if runF != nil { 72 return runF(opts) 73 } 74 return refreshRun(opts) 75 }, 76 } 77 78 cmd.Flags().StringVarP(&opts.Hostname, "hostname", "h", "", "The GitHub host to use for authentication") 79 cmd.Flags().StringSliceVarP(&opts.Scopes, "scopes", "s", nil, "Additional authentication scopes for gh to have") 80 81 return cmd 82 } 83 84 func refreshRun(opts *RefreshOptions) error { 85 cfg, err := opts.Config() 86 if err != nil { 87 return err 88 } 89 90 candidates := cfg.Hosts() 91 if len(candidates) == 0 { 92 return fmt.Errorf("not logged in to any hosts. Use 'gh auth login' to authenticate with a host") 93 } 94 95 hostname := opts.Hostname 96 if hostname == "" { 97 if len(candidates) == 1 { 98 hostname = candidates[0] 99 } else { 100 selected, err := opts.Prompter.Select("What account do you want to refresh auth for?", "", candidates) 101 if err != nil { 102 return fmt.Errorf("could not prompt: %w", err) 103 } 104 hostname = candidates[selected] 105 } 106 } else { 107 var found bool 108 for _, c := range candidates { 109 if c == hostname { 110 found = true 111 break 112 } 113 } 114 115 if !found { 116 return fmt.Errorf("not logged in to %s. use 'gh auth login' to authenticate with this host", hostname) 117 } 118 } 119 120 if src, writeable := shared.AuthTokenWriteable(cfg, hostname); !writeable { 121 fmt.Fprintf(opts.IO.ErrOut, "The value of the %s environment variable is being used for authentication.\n", src) 122 fmt.Fprint(opts.IO.ErrOut, "To refresh credentials stored in GitHub CLI, first clear the value from the environment.\n") 123 return cmdutil.SilentError 124 } 125 126 var additionalScopes []string 127 if oldToken, _ := cfg.AuthToken(hostname); oldToken != "" { 128 if oldScopes, err := shared.GetScopes(opts.HttpClient, hostname, oldToken); err == nil { 129 for _, s := range strings.Split(oldScopes, ",") { 130 s = strings.TrimSpace(s) 131 if s != "" { 132 additionalScopes = append(additionalScopes, s) 133 } 134 } 135 } 136 } 137 138 credentialFlow := &shared.GitCredentialFlow{ 139 Executable: opts.MainExecutable, 140 Prompter: opts.Prompter, 141 GitClient: opts.GitClient, 142 } 143 gitProtocol, _ := cfg.GetOrDefault(hostname, "git_protocol") 144 if opts.Interactive && gitProtocol == "https" { 145 if err := credentialFlow.Prompt(hostname); err != nil { 146 return err 147 } 148 additionalScopes = append(additionalScopes, credentialFlow.Scopes()...) 149 } 150 151 if err := opts.AuthFlow(cfg, opts.IO, hostname, append(opts.Scopes, additionalScopes...), opts.Interactive); err != nil { 152 return err 153 } 154 155 cs := opts.IO.ColorScheme() 156 fmt.Fprintf(opts.IO.ErrOut, "%s Authentication complete.\n", cs.SuccessIcon()) 157 158 if credentialFlow.ShouldSetup() { 159 username, _ := cfg.Get(hostname, "user") 160 password, _ := cfg.AuthToken(hostname) 161 if err := credentialFlow.Setup(hostname, username, password); err != nil { 162 return err 163 } 164 } 165 166 return nil 167 }