Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve browser login #265

Merged
merged 4 commits into from
Aug 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 {
ohm marked this conversation as resolved.
Show resolved Hide resolved
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