github.com/cli/cli@v1.14.1-0.20210902173923-1af6a669e342/pkg/cmd/auth/refresh/refresh.go (about)

     1  package refresh
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  
     7  	"github.com/AlecAivazis/survey/v2"
     8  	"github.com/MakeNowJust/heredoc"
     9  	"github.com/cli/cli/internal/authflow"
    10  	"github.com/cli/cli/internal/config"
    11  	"github.com/cli/cli/pkg/cmd/auth/shared"
    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 RefreshOptions struct {
    19  	IO     *iostreams.IOStreams
    20  	Config func() (config.Config, error)
    21  
    22  	MainExecutable string
    23  
    24  	Hostname string
    25  	Scopes   []string
    26  	AuthFlow func(config.Config, *iostreams.IOStreams, string, []string) error
    27  
    28  	Interactive bool
    29  }
    30  
    31  func NewCmdRefresh(f *cmdutil.Factory, runF func(*RefreshOptions) error) *cobra.Command {
    32  	opts := &RefreshOptions{
    33  		IO:     f.IOStreams,
    34  		Config: f.Config,
    35  		AuthFlow: func(cfg config.Config, io *iostreams.IOStreams, hostname string, scopes []string) error {
    36  			_, err := authflow.AuthFlowWithConfig(cfg, io, hostname, "", scopes)
    37  			return err
    38  		},
    39  		MainExecutable: f.Executable,
    40  	}
    41  
    42  	cmd := &cobra.Command{
    43  		Use:   "refresh",
    44  		Args:  cobra.ExactArgs(0),
    45  		Short: "Refresh stored authentication credentials",
    46  		Long: heredoc.Doc(`Expand or fix the permission scopes for stored credentials
    47  
    48  			The --scopes flag accepts a comma separated list of scopes you want your gh credentials to have. If
    49  			absent, this command ensures that gh has access to a minimum set of scopes.
    50  		`),
    51  		Example: heredoc.Doc(`
    52  			$ gh auth refresh --scopes write:org,read:public_key
    53  			# => open a browser to add write:org and read:public_key scopes for use with gh api
    54  
    55  			$ gh auth refresh
    56  			# => open a browser to ensure your authentication credentials have the correct minimum scopes
    57  		`),
    58  		RunE: func(cmd *cobra.Command, args []string) error {
    59  			opts.Interactive = opts.IO.CanPrompt()
    60  
    61  			if !opts.Interactive && opts.Hostname == "" {
    62  				return &cmdutil.FlagError{Err: errors.New("--hostname required when not running interactively")}
    63  			}
    64  
    65  			if runF != nil {
    66  				return runF(opts)
    67  			}
    68  			return refreshRun(opts)
    69  		},
    70  	}
    71  
    72  	cmd.Flags().StringVarP(&opts.Hostname, "hostname", "h", "", "The GitHub host to use for authentication")
    73  	cmd.Flags().StringSliceVarP(&opts.Scopes, "scopes", "s", nil, "Additional authentication scopes for gh to have")
    74  
    75  	return cmd
    76  }
    77  
    78  func refreshRun(opts *RefreshOptions) error {
    79  	cfg, err := opts.Config()
    80  	if err != nil {
    81  		return err
    82  	}
    83  
    84  	candidates, err := cfg.Hosts()
    85  	if err != nil {
    86  		return err
    87  	}
    88  	if len(candidates) == 0 {
    89  		return fmt.Errorf("not logged in to any hosts. Use 'gh auth login' to authenticate with a host")
    90  	}
    91  
    92  	hostname := opts.Hostname
    93  	if hostname == "" {
    94  		if len(candidates) == 1 {
    95  			hostname = candidates[0]
    96  		} else {
    97  			err := prompt.SurveyAskOne(&survey.Select{
    98  				Message: "What account do you want to refresh auth for?",
    99  				Options: candidates,
   100  			}, &hostname)
   101  
   102  			if err != nil {
   103  				return fmt.Errorf("could not prompt: %w", err)
   104  			}
   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 err := cfg.CheckWriteable(hostname, "oauth_token"); err != nil {
   121  		var roErr *config.ReadOnlyEnvError
   122  		if errors.As(err, &roErr) {
   123  			fmt.Fprintf(opts.IO.ErrOut, "The value of the %s environment variable is being used for authentication.\n", roErr.Variable)
   124  			fmt.Fprint(opts.IO.ErrOut, "To refresh credentials stored in GitHub CLI, first clear the value from the environment.\n")
   125  			return cmdutil.SilentError
   126  		}
   127  		return err
   128  	}
   129  
   130  	var additionalScopes []string
   131  
   132  	credentialFlow := &shared.GitCredentialFlow{}
   133  	gitProtocol, _ := cfg.Get(hostname, "git_protocol")
   134  	if opts.Interactive && gitProtocol == "https" {
   135  		if err := credentialFlow.Prompt(hostname); err != nil {
   136  			return err
   137  		}
   138  		additionalScopes = append(additionalScopes, credentialFlow.Scopes()...)
   139  	}
   140  
   141  	if err := opts.AuthFlow(cfg, opts.IO, hostname, append(opts.Scopes, additionalScopes...)); err != nil {
   142  		return err
   143  	}
   144  
   145  	if credentialFlow.ShouldSetup() {
   146  		username, _ := cfg.Get(hostname, "user")
   147  		password, _ := cfg.Get(hostname, "oauth_token")
   148  		if err := credentialFlow.Setup(hostname, username, password); err != nil {
   149  			return err
   150  		}
   151  	}
   152  
   153  	return nil
   154  }