Skip to content

Commit

Permalink
Allow for arbitrary OAuth hosts
Browse files Browse the repository at this point in the history
Not all OAuth hosts use the same routes as GitHub, for example:
- Microsoft use /oauth2/v2.0/devicecode
- Google use /device/code
- Auth0 use /oauth/device/code

Similar differences are present for the authorise and access token
routes too.

This commit introduces a concept of a Host, which is a container for
the endpoints that the library uses. After this, the Hostname field is
deprecated.

Co-authored-by: Mislav Marohnić <mislav@github.com>
  • Loading branch information
jamierocks and mislav committed Oct 15, 2021
1 parent 63e88b3 commit 6dfce11
Show file tree
Hide file tree
Showing 5 changed files with 38 additions and 18 deletions.
2 changes: 1 addition & 1 deletion examples_test.go
Expand Up @@ -10,7 +10,7 @@ import (
// flow support is globally available, but enables logging in to hosted GitHub instances as well.
func Example() {
flow := &Flow{
Hostname: "github.com",
Host: GitHubHost("https://github.com"),
ClientID: os.Getenv("OAUTH_CLIENT_ID"),
ClientSecret: os.Getenv("OAUTH_CLIENT_SECRET"), // only applicable to web app flow
CallbackURI: "http://127.0.0.1/callback", // only applicable to web app flow
Expand Down
1 change: 1 addition & 0 deletions go.sum
@@ -1,3 +1,4 @@
github.com/cli/browser v1.0.0 h1:RIleZgXrhdiCVgFBSjtWwkLPUCWyhhhN5k5HGSBt1js=
github.com/cli/browser v1.0.0/go.mod h1:IEWkHYbLjkhtjwwWlwTHW2lGxeS5gezEQBMLTwDHf5Q=
github.com/cli/safeexec v1.0.0 h1:0VngyaIyqACHdcMNWfo6+KdUYnqEr2Sg+bSP1pdF+dI=
github.com/cli/safeexec v1.0.0/go.mod h1:Z/D4tTN8Vs5gXYHDCbaM1S/anmEDnJb1iW0+EJ5zx3Q=
36 changes: 23 additions & 13 deletions oauth.go
Expand Up @@ -17,10 +17,32 @@ type httpClient interface {
PostForm(string, url.Values) (*http.Response, error)
}

// Host defines the endpoints used to authorize against an OAuth server.
type Host struct {
DeviceCodeURL string
AuthorizeURL string
TokenURL string
}

// GitHubHost constructs a Host from the given URL to a GitHub instance.
func GitHubHost(hostURL string) *Host {
u, _ := url.Parse(hostURL)

return &Host{
DeviceCodeURL: fmt.Sprintf("%s://%s/login/device/code", u.Scheme, u.Host),
AuthorizeURL: fmt.Sprintf("%s://%s/login/oauth/authorize", u.Scheme, u.Host),
TokenURL: fmt.Sprintf("%s://%s/login/oauth/access_token", u.Scheme, u.Host),
}
}

// Flow facilitates a single OAuth authorization flow.
type Flow struct {
// The host to authorize the app with.
// The hostname to authorize the app with.
//
// Deprecated: Use Host instead.
Hostname string
// Host configuration to authorize the app with.
Host *Host
// OAuth scopes to request from the user.
Scopes []string
// OAuth application ID.
Expand All @@ -47,18 +69,6 @@ type Flow struct {
Stdout io.Writer
}

func deviceInitURL(host string) string {
return fmt.Sprintf("https://%s/login/device/code", host)
}

func webappInitURL(host string) string {
return fmt.Sprintf("https://%s/login/oauth/authorize", host)
}

func tokenURL(host string) string {
return fmt.Sprintf("https://%s/login/oauth/access_token", host)
}

// DetectFlow tries to perform Device flow first and falls back to Web application flow.
func (oa *Flow) DetectFlow() (*api.AccessToken, error) {
accessToken, err := oa.DeviceFlow()
Expand Down
8 changes: 6 additions & 2 deletions oauth_device.go
Expand Up @@ -28,8 +28,12 @@ func (oa *Flow) DeviceFlow() (*api.AccessToken, error) {
if stdout == nil {
stdout = os.Stdout
}
host := oa.Host
if host == nil {
host = GitHubHost("https://" + oa.Hostname)
}

code, err := device.RequestCode(httpClient, deviceInitURL(oa.Hostname), oa.ClientID, oa.Scopes)
code, err := device.RequestCode(httpClient, host.DeviceCodeURL, oa.ClientID, oa.Scopes)
if err != nil {
return nil, err
}
Expand All @@ -54,7 +58,7 @@ func (oa *Flow) DeviceFlow() (*api.AccessToken, error) {
return nil, fmt.Errorf("error opening the web browser: %w", err)
}

return device.PollToken(httpClient, tokenURL(oa.Hostname), oa.ClientID, code)
return device.PollToken(httpClient, host.TokenURL, oa.ClientID, code)
}

func waitForEnter(r io.Reader) error {
Expand Down
9 changes: 7 additions & 2 deletions oauth_webapp.go
Expand Up @@ -12,6 +12,11 @@ import (
// WebAppFlow starts a local HTTP server, opens the web browser to initiate the OAuth Web application
// flow, blocks until the user completes authorization and is redirected back, and returns the access token.
func (oa *Flow) WebAppFlow() (*api.AccessToken, error) {
host := oa.Host
if host == nil {
host = GitHubHost("https://" + oa.Hostname)
}

flow, err := webapp.InitFlow()
if err != nil {
return nil, err
Expand All @@ -23,7 +28,7 @@ func (oa *Flow) WebAppFlow() (*api.AccessToken, error) {
Scopes: oa.Scopes,
AllowSignup: true,
}
browserURL, err := flow.BrowserURL(webappInitURL(oa.Hostname), params)
browserURL, err := flow.BrowserURL(host.AuthorizeURL, params)
if err != nil {
return nil, err
}
Expand All @@ -47,5 +52,5 @@ func (oa *Flow) WebAppFlow() (*api.AccessToken, error) {
httpClient = http.DefaultClient
}

return flow.AccessToken(httpClient, tokenURL(oa.Hostname), oa.ClientSecret)
return flow.AccessToken(httpClient, host.TokenURL, oa.ClientSecret)
}

0 comments on commit 6dfce11

Please sign in to comment.