Skip to content

Commit

Permalink
Improve browser login (#265)
Browse files Browse the repository at this point in the history
* Print full auth URL

* Make browser open optional

* Allow configuring optional browser open

* Add changelog entry
  • Loading branch information
ohm authored Aug 23, 2024
1 parent 8439b9b commit 050fedb
Show file tree
Hide file tree
Showing 6 changed files with 43 additions and 9 deletions.
3 changes: 3 additions & 0 deletions .changelog/265.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
```release-note:improvement
Browser auth flow prints URL and can be configured to not open the default browser.
```
24 changes: 16 additions & 8 deletions auth/browser.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,29 +22,30 @@ import (
// browserLogin implements an oauth2.TokenSource for interactive browser logins.
type browserLogin struct {
oauthConfig *oauth2.Config

// Wether to open a browser in an external window or not.
openBrowser bool
}

// NewBrowserLogin will return an oauth2.TokenSource that will return a Token from an interactive browser login.
func NewBrowserLogin(oauthConfig *oauth2.Config) *browserLogin {
func NewBrowserLogin(oauthConfig *oauth2.Config, openBrowser bool) *browserLogin {
return &browserLogin{
oauthConfig: oauthConfig,
openBrowser: openBrowser,
}
}

// Token will return an oauth2.Token retrieved from an interactive browser login.
func (b *browserLogin) Token() (*oauth2.Token, error) {
browser := &oauthBrowser{}
return browser.GetTokenFromBrowser(context.Background(), b.oauthConfig)
return browser.GetTokenFromBrowser(context.Background(), b.oauthConfig, b.openBrowser)
}

// oauthBrowser implements the Browser interface using the real OAuth2 login flow.
type oauthBrowser struct{}

// GetTokenFromBrowser opens a browser window for the user to log in and handles the OAuth2 flow to obtain a token.
func (b *oauthBrowser) GetTokenFromBrowser(ctx context.Context, conf *oauth2.Config) (*oauth2.Token, error) {
// Launch a request to Auth0's authorization endpoint.
colorstring.Printf("[bold][yellow]The default web browser has been opened at %s. Please continue the login in the web browser.\n", conf.Endpoint.AuthURL)

func (b *oauthBrowser) GetTokenFromBrowser(ctx context.Context, conf *oauth2.Config, openBrowser bool) (*oauth2.Token, error) {
// Prepare the /authorize request with randomly generated state, offline access option, and audience
aud := "https://api.hashicorp.cloud"
opt := oauth2.SetAuthURLParam("audience", aud)
Expand All @@ -55,8 +56,15 @@ func (b *oauthBrowser) GetTokenFromBrowser(ctx context.Context, conf *oauth2.Con
signal.Notify(sigintCh, os.Interrupt)
defer signal.Stop(sigintCh)

if err := open.Start(authzURL); err != nil {
return nil, fmt.Errorf("failed to open browser at URL %q: %w", authzURL, err)
// Launch a request to HCP's authorization endpoint.
if openBrowser {
colorstring.Printf("[bold][yellow]The default web browser has been opened at %s. Please continue the login in the web browser.\n", authzURL)

if err := open.Start(authzURL); err != nil {
return nil, fmt.Errorf("failed to open browser at URL %q: %w", authzURL, err)
}
} else {
colorstring.Printf("[bold][yellow]Please open the following URL in your browser and follow the instructions to authenticate:\n%s\n", authzURL)
}

// Start callback server
Expand Down
3 changes: 3 additions & 0 deletions config/hcp.go
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,9 @@ type hcpConfig struct {
// noBrowserLogin is an option to prevent automatic browser login when no local credentials are found.
noBrowserLogin bool

// noDefaultBrowser is an option to prevent the browser login from opening the default browser.
noDefaultBrowser bool

// suppressLogging is an option to prevent this SDK from printing anything
suppressLogging bool

Expand Down
2 changes: 1 addition & 1 deletion config/tokensource.go
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,7 @@ func (c *hcpConfig) getTokenSource() (oauth2.TokenSource, sourceType, string, er

var loginTokenSource oauth2.TokenSource
if !c.noBrowserLogin {
loginTokenSource = auth.NewBrowserLogin(&c.oauth2Config)
loginTokenSource = auth.NewBrowserLogin(&c.oauth2Config, c.noDefaultBrowser)
}

return loginTokenSource, sourceTypeLogin, "", nil
Expand Down
9 changes: 9 additions & 0 deletions config/with.go
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,15 @@ func WithoutBrowserLogin() HCPConfigOption {
}
}

// WithoutOpenDefaultBrowser disables opening the default browser when
// browser login is enabled.
func WithoutOpenDefaultBrowser() HCPConfigOption {
return func(config *hcpConfig) error {
config.noDefaultBrowser = true
return nil
}
}

// WithoutLogging disables this SDK from printing of any kind, this is necessary
// since there is not a consistent logger that is used throughout the project so
// a log level option is not sufficient.
Expand Down
11 changes: 11 additions & 0 deletions config/with_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,17 @@ func TestWithout_BrowserLogin(t *testing.T) {
require.True(config.noBrowserLogin)
}

func TestWithout_OpenDefaultBrowser(t *testing.T) {
require := requirepkg.New(t)

// Exercise
config := &hcpConfig{}
require.NoError(apply(config, WithoutOpenDefaultBrowser()))

// Ensure browser login doesn't use the default browser
require.True(config.noDefaultBrowser)
}

func TestWith_CredentialFile(t *testing.T) {
require := requirepkg.New(t)

Expand Down

0 comments on commit 050fedb

Please sign in to comment.