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  }