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 Server, which is a container for
the endpoints that the library uses. This is a replacement for Flow's
Hostname and as such is a breaking change.
  • Loading branch information
jamierocks committed Apr 3, 2021
1 parent 6b1e71c commit f335141
Show file tree
Hide file tree
Showing 6 changed files with 32 additions and 20 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",
Server: ServerGitHub("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=
17 changes: 2 additions & 15 deletions oauth.go
Expand Up @@ -4,7 +4,6 @@ package oauth

import (
"errors"
"fmt"
"io"
"net/http"
"net/url"
Expand All @@ -19,8 +18,8 @@ type httpClient interface {

// Flow facilitates a single OAuth authorization flow.
type Flow struct {
// The host to authorize the app with.
Hostname string
// The server to authorize the app with.
Server Server
// OAuth scopes to request from the user.
Scopes []string
// OAuth application ID.
Expand All @@ -47,18 +46,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
4 changes: 2 additions & 2 deletions oauth_device.go
Expand Up @@ -29,7 +29,7 @@ func (oa *Flow) DeviceFlow() (*api.AccessToken, error) {
stdout = os.Stdout
}

code, err := device.RequestCode(httpClient, deviceInitURL(oa.Hostname), oa.ClientID, oa.Scopes)
code, err := device.RequestCode(httpClient, oa.Server.DeviceCodeURL, oa.ClientID, oa.Scopes)
if err != nil {
return nil, err
}
Expand All @@ -54,7 +54,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, oa.Server.TokenURL, oa.ClientID, code)
}

func waitForEnter(r io.Reader) error {
Expand Down
4 changes: 2 additions & 2 deletions oauth_webapp.go
Expand Up @@ -23,7 +23,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(oa.Server.AuthoriseURL, params)
if err != nil {
return nil, err
}
Expand All @@ -47,5 +47,5 @@ func (oa *Flow) WebAppFlow() (*api.AccessToken, error) {
httpClient = http.DefaultClient
}

return flow.AccessToken(httpClient, tokenURL(oa.Hostname), oa.ClientSecret)
return flow.AccessToken(httpClient, oa.Server.TokenURL, oa.ClientSecret)
}
24 changes: 24 additions & 0 deletions server.go
@@ -0,0 +1,24 @@
package oauth

import "fmt"

// Server represents the endpoints used to authorise against an OAuth server.
type Server struct {
DeviceCodeURL string
AuthoriseURL string
TokenURL string
}

// ServerGitHub gets a Server for the given GitHub (Enterprise) host. If host is empty,
// it will default to "github.com".
func ServerGitHub(host string) Server {
if host == "" {
host = "github.com"
}

return Server{
DeviceCodeURL: fmt.Sprintf("https://%s/login/device/code", host),
AuthoriseURL: fmt.Sprintf("https://%s/login/oauth/authorize", host),
TokenURL: fmt.Sprintf("https://%s/login/oauth/access_token", host),
}
}

0 comments on commit f335141

Please sign in to comment.