diff --git a/examples_test.go b/examples_test.go index 336c72a..975c8c9 100644 --- a/examples_test.go +++ b/examples_test.go @@ -12,8 +12,12 @@ import ( // github.com, as its Device flow support is globally available, but it enables logging in to // self-hosted GitHub instances as well. func ExampleFlow_DetectFlow() { + host, err := oauth.NewGitHubHost("https://github.com") + if err != nil { + panic(err) + } flow := &oauth.Flow{ - Host: oauth.GitHubHost("https://github.com"), + Host: host, 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 diff --git a/oauth.go b/oauth.go index 6439782..7e38c71 100644 --- a/oauth.go +++ b/oauth.go @@ -8,6 +8,7 @@ import ( "io" "net/http" "net/url" + "strings" "github.com/cli/oauth/api" "github.com/cli/oauth/device" @@ -24,7 +25,29 @@ type Host struct { TokenURL string } +// NewGitHubHost constructs a Host from the given URL to a GitHub instance. +func NewGitHubHost(hostURL string) (*Host, error) { + base, err := url.Parse(strings.TrimSpace(hostURL)) + if err != nil { + return nil, err + } + + createURL := func(path string) string { + u := *base // Copy base URL + u.Path = path + return u.String() + } + + return &Host{ + DeviceCodeURL: createURL("/login/device/code"), + AuthorizeURL: createURL("/login/oauth/authorize"), + TokenURL: createURL("/login/oauth/access_token"), + }, nil +} + // GitHubHost constructs a Host from the given URL to a GitHub instance. +// +// Deprecated: `GitHubHost` can panic with a malformed `hostURL`. Use `NewGitHubHost` instead for graceful error handling. func GitHubHost(hostURL string) *Host { u, _ := url.Parse(hostURL) diff --git a/oauth_device.go b/oauth_device.go index d4615ef..3e39877 100644 --- a/oauth_device.go +++ b/oauth_device.go @@ -29,9 +29,14 @@ func (oa *Flow) DeviceFlow() (*api.AccessToken, error) { if stdout == nil { stdout = os.Stdout } + host := oa.Host if host == nil { - host = GitHubHost("https://" + oa.Hostname) + host, err := NewGitHubHost("https://" + oa.Hostname) + if err != nil { + return nil, fmt.Errorf("error parsing the hostname '%s': %w", host, err) + } + oa.Host = host } code, err := device.RequestCode(httpClient, host.DeviceCodeURL, oa.ClientID, oa.Scopes) diff --git a/oauth_webapp.go b/oauth_webapp.go index e448715..22a77aa 100644 --- a/oauth_webapp.go +++ b/oauth_webapp.go @@ -14,8 +14,13 @@ import ( // 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) + host, err := NewGitHubHost("https://" + oa.Hostname) + if err != nil { + return nil, fmt.Errorf("error parsing the hostname '%s': %w", host, err) + } + oa.Host = host } flow, err := webapp.InitFlow()