github.com/abdfnx/gh-api@v0.0.0-20210414084727-f5432eec23b8/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/abdfnx/gh-api/internal/authflow"
    10  	"github.com/abdfnx/gh-api/internal/config"
    11  	"github.com/abdfnx/gh-api/pkg/cmd/auth/shared"
    12  	"github.com/abdfnx/gh-api/pkg/cmdutil"
    13  	"github.com/abdfnx/gh-api/pkg/iostreams"
    14  	"github.com/abdfnx/gh-api/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 fmt.Errorf("not logged in to any hosts. Use 'gh auth login' to authenticate with a host")
    87  	}
    88  
    89  	hostname := opts.Hostname
    90  	if hostname == "" {
    91  		if len(candidates) == 1 {
    92  			hostname = candidates[0]
    93  		} else {
    94  			err := prompt.SurveyAskOne(&survey.Select{
    95  				Message: "What account do you want to refresh auth for?",
    96  				Options: candidates,
    97  			}, &hostname)
    98  
    99  			if err != nil {
   100  				return fmt.Errorf("could not prompt: %w", err)
   101  			}
   102  		}
   103  	} else {
   104  		var found bool
   105  		for _, c := range candidates {
   106  			if c == hostname {
   107  				found = true
   108  				break
   109  			}
   110  		}
   111  
   112  		if !found {
   113  			return fmt.Errorf("not logged in to %s. use 'gh auth login' to authenticate with this host", hostname)
   114  		}
   115  	}
   116  
   117  	if err := cfg.CheckWriteable(hostname, "oauth_token"); err != nil {
   118  		var roErr *config.ReadOnlyEnvError
   119  		if errors.As(err, &roErr) {
   120  			fmt.Fprintf(opts.IO.ErrOut, "The value of the %s environment variable is being used for authentication.\n", roErr.Variable)
   121  			fmt.Fprint(opts.IO.ErrOut, "To refresh credentials stored in GitHub CLI, first clear the value from the environment.\n")
   122  			return cmdutil.SilentError
   123  		}
   124  		return err
   125  	}
   126  
   127  	var additionalScopes []string
   128  
   129  	credentialFlow := &shared.GitCredentialFlow{}
   130  	gitProtocol, _ := cfg.Get(hostname, "git_protocol")
   131  	if opts.Interactive && gitProtocol == "https" {
   132  		if err := credentialFlow.Prompt(hostname); err != nil {
   133  			return err
   134  		}
   135  		additionalScopes = append(additionalScopes, credentialFlow.Scopes()...)
   136  	}
   137  
   138  	if err := opts.AuthFlow(cfg, opts.IO, hostname, append(opts.Scopes, additionalScopes...)); err != nil {
   139  		return err
   140  	}
   141  
   142  	if credentialFlow.ShouldSetup() {
   143  		username, _ := cfg.Get(hostname, "user")
   144  		password, _ := cfg.Get(hostname, "oauth_token")
   145  		if err := credentialFlow.Setup(hostname, username, password); err != nil {
   146  			return err
   147  		}
   148  	}
   149  
   150  	return nil
   151  }