From 2345f49ccbd6f1e902b9eae305d93494db30ec38 Mon Sep 17 00:00:00 2001 From: vilmibm Date: Thu, 10 Sep 2020 20:58:06 -0500 Subject: [PATCH] support auth login --web --- pkg/cmd/auth/login/login.go | 116 ++++++++++++++++++------------- pkg/cmd/auth/login/login_test.go | 49 +++++++++++-- 2 files changed, 113 insertions(+), 52 deletions(-) diff --git a/pkg/cmd/auth/login/login.go b/pkg/cmd/auth/login/login.go index 630677dff..10a79c685 100644 --- a/pkg/cmd/auth/login/login.go +++ b/pkg/cmd/auth/login/login.go @@ -24,8 +24,11 @@ type LoginOptions struct { IO *iostreams.IOStreams Config func() (config.Config, error) + Interactive bool + Hostname string Token string + Web bool } func NewCmdLogin(f *cmdutil.Factory, runF func(*LoginOptions) error) *cobra.Command { @@ -51,6 +54,9 @@ func NewCmdLogin(f *cmdutil.Factory, runF func(*LoginOptions) error) *cobra.Comm $ gh auth login # => do an interactive setup + $ gh auth login --web + # => open a browser to authenticate and do a non-interactive setup + $ gh auth login --with-token < mytoken.txt # => read token from mytoken.txt and authenticate against github.com @@ -58,6 +64,14 @@ func NewCmdLogin(f *cmdutil.Factory, runF func(*LoginOptions) error) *cobra.Comm # => read token from mytoken.txt and authenticate against a GitHub Enterprise Server instance `), RunE: func(cmd *cobra.Command, args []string) error { + if !opts.IO.CanPrompt() && !(tokenStdin || opts.Web) { + return &cmdutil.FlagError{Err: errors.New("--web or --with-token required when not running interactively")} + } + + if tokenStdin && opts.Web { + return &cmdutil.FlagError{Err: errors.New("specify only one of --web or --with-token")} + } + if tokenStdin { defer opts.IO.In.Close() token, err := ioutil.ReadAll(opts.IO.In) @@ -67,15 +81,8 @@ func NewCmdLogin(f *cmdutil.Factory, runF func(*LoginOptions) error) *cobra.Comm opts.Token = strings.TrimSpace(string(token)) } - if opts.Token != "" { - // Assume non-interactive if a token is specified - if opts.Hostname == "" { - opts.Hostname = ghinstance.Default() - } - } else { - if !opts.IO.CanPrompt() { - return &cmdutil.FlagError{Err: errors.New("--with-token required when not running interactively")} - } + if opts.IO.CanPrompt() && opts.Token == "" && !opts.Web { + opts.Interactive = true } if cmd.Flags().Changed("hostname") { @@ -84,6 +91,12 @@ func NewCmdLogin(f *cmdutil.Factory, runF func(*LoginOptions) error) *cobra.Comm } } + if !opts.Interactive { + if opts.Hostname == "" { + opts.Hostname = ghinstance.Default() + } + } + if runF != nil { return runF(opts) } @@ -94,6 +107,7 @@ func NewCmdLogin(f *cmdutil.Factory, runF func(*LoginOptions) error) *cobra.Comm cmd.Flags().StringVarP(&opts.Hostname, "hostname", "h", "", "The hostname of the GitHub instance to authenticate with") cmd.Flags().BoolVar(&tokenStdin, "with-token", false, "Read token from standard input") + cmd.Flags().BoolVarP(&opts.Web, "web", "w", false, "Open a browser to authenticate") return cmd } @@ -168,24 +182,26 @@ func loginRun(opts *LoginOptions) error { return err } - username, err := api.CurrentLoginName(apiClient, hostname) - if err != nil { - return fmt.Errorf("error using api: %w", err) - } - var keepGoing bool - err = prompt.SurveyAskOne(&survey.Confirm{ - Message: fmt.Sprintf( - "You're already logged into %s as %s. Do you want to re-authenticate?", - hostname, - username), - Default: false, - }, &keepGoing) - if err != nil { - return fmt.Errorf("could not prompt: %w", err) - } + if opts.Interactive { + username, err := api.CurrentLoginName(apiClient, hostname) + if err != nil { + return fmt.Errorf("error using api: %w", err) + } + var keepGoing bool + err = prompt.SurveyAskOne(&survey.Confirm{ + Message: fmt.Sprintf( + "You're already logged into %s as %s. Do you want to re-authenticate?", + hostname, + username), + Default: false, + }, &keepGoing) + if err != nil { + return fmt.Errorf("could not prompt: %w", err) + } - if !keepGoing { - return nil + if !keepGoing { + return nil + } } } } @@ -195,15 +211,19 @@ func loginRun(opts *LoginOptions) error { } var authMode int - err = prompt.SurveyAskOne(&survey.Select{ - Message: "How would you like to authenticate?", - Options: []string{ - "Login with a web browser", - "Paste an authentication token", - }, - }, &authMode) - if err != nil { - return fmt.Errorf("could not prompt: %w", err) + if opts.Web { + authMode = 0 + } else { + err = prompt.SurveyAskOne(&survey.Select{ + Message: "How would you like to authenticate?", + Options: []string{ + "Login with a web browser", + "Paste an authentication token", + }, + }, &authMode) + if err != nil { + return fmt.Errorf("could not prompt: %w", err) + } } if authMode == 0 { @@ -239,19 +259,21 @@ func loginRun(opts *LoginOptions) error { } } - var gitProtocol string - err = prompt.SurveyAskOne(&survey.Select{ - Message: "Choose default git protocol", - Options: []string{ - "HTTPS", - "SSH", - }, - }, &gitProtocol) - if err != nil { - return fmt.Errorf("could not prompt: %w", err) - } + gitProtocol := "https" + if opts.Interactive { + err = prompt.SurveyAskOne(&survey.Select{ + Message: "Choose default git protocol", + Options: []string{ + "HTTPS", + "SSH", + }, + }, &gitProtocol) + if err != nil { + return fmt.Errorf("could not prompt: %w", err) + } - gitProtocol = strings.ToLower(gitProtocol) + gitProtocol = strings.ToLower(gitProtocol) + } fmt.Fprintf(opts.IO.ErrOut, "- gh config set -h %s git_protocol %s\n", hostname, gitProtocol) err = cfg.Set(hostname, "git_protocol", gitProtocol) diff --git a/pkg/cmd/auth/login/login_test.go b/pkg/cmd/auth/login/login_test.go index 5d0ba1942..7166eac92 100644 --- a/pkg/cmd/auth/login/login_test.go +++ b/pkg/cmd/auth/login/login_test.go @@ -81,8 +81,9 @@ func Test_NewCmdLogin(t *testing.T) { stdinTTY: true, cli: "--hostname barry.burton", wants: LoginOptions{ - Hostname: "barry.burton", - Token: "", + Hostname: "barry.burton", + Token: "", + Interactive: true, }, }, { @@ -90,10 +91,33 @@ func Test_NewCmdLogin(t *testing.T) { stdinTTY: true, cli: "", wants: LoginOptions{ - Hostname: "", - Token: "", + Hostname: "", + Token: "", + Interactive: true, }, }, + { + name: "tty web", + stdinTTY: true, + cli: "--web", + wants: LoginOptions{ + Hostname: "github.com", + Web: true, + }, + }, + { + name: "nontty web", + cli: "--web", + wants: LoginOptions{ + Hostname: "github.com", + Web: true, + }, + }, + { + name: "web and with-token", + cli: "--web --with-token", + wantsErr: true, + }, } for _, tt := range tests { @@ -134,6 +158,8 @@ func Test_NewCmdLogin(t *testing.T) { assert.Equal(t, tt.wants.Token, gotOpts.Token) assert.Equal(t, tt.wants.Hostname, gotOpts.Hostname) + assert.Equal(t, tt.wants.Web, gotOpts.Web) + assert.Equal(t, tt.wants.Interactive, gotOpts.Interactive) }) } } @@ -262,6 +288,9 @@ func Test_loginRun_Survey(t *testing.T) { }{ { name: "already authenticated", + opts: &LoginOptions{ + Interactive: true, + }, cfg: func(cfg config.Config) { _ = cfg.Set("github.com", "oauth_token", "ghi789") }, @@ -280,7 +309,8 @@ func Test_loginRun_Survey(t *testing.T) { { name: "hostname set", opts: &LoginOptions{ - Hostname: "rebecca.chambers", + Hostname: "rebecca.chambers", + Interactive: true, }, wantHosts: "rebecca.chambers:\n oauth_token: def456\n git_protocol: https\n user: jillv\n", askStubs: func(as *prompt.AskStubber) { @@ -298,6 +328,9 @@ func Test_loginRun_Survey(t *testing.T) { { name: "choose enterprise", wantHosts: "brad.vickers:\n oauth_token: def456\n git_protocol: https\n user: jillv\n", + opts: &LoginOptions{ + Interactive: true, + }, askStubs: func(as *prompt.AskStubber) { as.StubOne(1) // host type enterprise as.StubOne("brad.vickers") // hostname @@ -315,6 +348,9 @@ func Test_loginRun_Survey(t *testing.T) { { name: "choose github.com", wantHosts: "github.com:\n oauth_token: def456\n git_protocol: https\n user: jillv\n", + opts: &LoginOptions{ + Interactive: true, + }, askStubs: func(as *prompt.AskStubber) { as.StubOne(0) // host type github.com as.StubOne(1) // auth mode: token @@ -325,6 +361,9 @@ func Test_loginRun_Survey(t *testing.T) { { name: "sets git_protocol", wantHosts: "github.com:\n oauth_token: def456\n git_protocol: ssh\n user: jillv\n", + opts: &LoginOptions{ + Interactive: true, + }, askStubs: func(as *prompt.AskStubber) { as.StubOne(0) // host type github.com as.StubOne(1) // auth mode: token