chore: gh-host as oauth auth server#2046
Conversation
When gh-host is not set (github.com users), the AuthorizationServer field was being set to just "login/oauth" (empty host + path), breaking OAuth metadata. Now it only overrides the default when gh-host is provided (GHES users), allowing github.com users to get the correct default of "https://github.com/login/oauth". Co-authored-by: atharva1051 <53966412+atharva1051@users.noreply.github.com>
…rver-condition Fix: Only set custom OAuth AuthorizationServer when gh-host is configured
There was a problem hiding this comment.
Pull request overview
Adjusts HTTP-mode OAuth protected resource metadata so the authorization server URL is only overridden when --gh-host is explicitly provided, allowing github.com users to fall back to the built-in default authorization server.
Changes:
- Conditionally set
oauthCfg.AuthorizationServeronly whencfg.Host != ""(otherwise rely onoauth.NewAuthHandler’s default). - Avoid generating an invalid authorization server value when
cfg.Hostis empty.
|
Hello @SamMorrowDrums or @omgitsads , Could you please review this once? Thank you |
|
Hi @atharva1051, thanks for raising this issue. I think theres some additional complexity around handling GHES for both Like I said in the review, I think a better place to make this decision is in during the url resolution in the |
|
Oh that would be really helpful, although i did end up making few changes the moment I saw your comment. |
| BaseRESTURL(ctx context.Context) (*url.URL, error) | ||
| GraphqlURL(ctx context.Context) (*url.URL, error) | ||
| UploadURL(ctx context.Context) (*url.URL, error) | ||
| RawURL(ctx context.Context) (*url.URL, error) | ||
| OAuthURL(ctx context.Context) (*url.URL, error) |
There was a problem hiding this comment.
Adding OAuthURL to the exported utils.APIHostResolver interface is a source-breaking change for any downstream code that implements this interface. To avoid breaking external consumers, consider keeping APIHostResolver focused on API endpoints and introducing a separate, smaller interface for OAuth resolution (e.g., an interface with just OAuthURL), then have utils.APIHost satisfy both.
| oauthURL, err := cfg.APIHost.OAuthURL(context.Background()) | ||
| if err != nil { | ||
| return nil, fmt.Errorf("failed to get OAuth URL from API host: %w", err) | ||
| } |
There was a problem hiding this comment.
NewAuthHandler assumes APIHost.OAuthURL returns a non-nil *url.URL; if an implementation returns (nil, nil), oauthURL.String() will panic. Please add a nil check (and return a descriptive error) before dereferencing, and consider validating the URL has scheme/host to avoid emitting invalid metadata.
| } | |
| } | |
| if oauthURL == nil { | |
| return nil, fmt.Errorf("API host returned nil OAuth URL") | |
| } | |
| if oauthURL.Scheme == "" || oauthURL.Host == "" { | |
| return nil, fmt.Errorf("API host returned invalid OAuth URL: missing scheme or host") | |
| } |
| name: "GHES with HTTP and custom port (port stripped - not supported yet)", | ||
| host: "http://ghes.local:8080", | ||
| expectedOAuth: "http://ghes.local/login/oauth", // Port is stripped ref: ln222 api.go comment | ||
| }, | ||
| { | ||
| name: "GHES with HTTPS and custom port (port stripped - not supported yet)", | ||
| host: "https://ghes.local:8443", | ||
| expectedOAuth: "https://ghes.local/login/oauth", // Port is stripped ref: ln222 api.go comment |
There was a problem hiding this comment.
This test case codifies the current behavior of silently stripping ports from the configured host. Since parseAPIHost explicitly notes ports are not handled, accepting a host with a port but then discarding it can lead to requests being sent to the wrong endpoint. Prefer returning an error when a port is present (until ports are supported), and update this test to expect an error instead of a rewritten URL.
| name: "GHES with HTTP and custom port (port stripped - not supported yet)", | |
| host: "http://ghes.local:8080", | |
| expectedOAuth: "http://ghes.local/login/oauth", // Port is stripped ref: ln222 api.go comment | |
| }, | |
| { | |
| name: "GHES with HTTPS and custom port (port stripped - not supported yet)", | |
| host: "https://ghes.local:8443", | |
| expectedOAuth: "https://ghes.local/login/oauth", // Port is stripped ref: ln222 api.go comment | |
| name: "GHES with HTTP and custom port (should error - ports not supported)", | |
| host: "http://ghes.local:8080", | |
| expectError: true, | |
| }, | |
| { | |
| name: "GHES with HTTPS and custom port (should error - ports not supported)", | |
| host: "https://ghes.local:8443", | |
| expectError: true, |
| expectedOAuth: "http://ghes.local/login/oauth", // Port is stripped ref: ln222 api.go comment | ||
| }, | ||
| { | ||
| name: "GHES with HTTPS and custom port (port stripped - not supported yet)", | ||
| host: "https://ghes.local:8443", | ||
| expectedOAuth: "https://ghes.local/login/oauth", // Port is stripped ref: ln222 api.go comment |
There was a problem hiding this comment.
Referencing a specific line number in another file ("ref: ln222") is brittle and will quickly go stale as the file changes. Prefer referencing the function name (parseAPIHost) or quoting the relevant comment text instead.
| expectedOAuth: "http://ghes.local/login/oauth", // Port is stripped ref: ln222 api.go comment | |
| }, | |
| { | |
| name: "GHES with HTTPS and custom port (port stripped - not supported yet)", | |
| host: "https://ghes.local:8443", | |
| expectedOAuth: "https://ghes.local/login/oauth", // Port is stripped ref: ln222 api.go comment | |
| expectedOAuth: "http://ghes.local/login/oauth", // Port is stripped; ports are not yet supported by parseAPIHost | |
| }, | |
| { | |
| name: "GHES with HTTPS and custom port (port stripped - not supported yet)", | |
| host: "https://ghes.local:8443", | |
| expectedOAuth: "https://ghes.local/login/oauth", // Port is stripped; ports are not yet supported by parseAPIHost |
| expectedOAuth: "http://ghes.local/login/oauth", // Port is stripped ref: ln222 api.go comment | ||
| }, | ||
| { | ||
| name: "GHES with HTTPS and custom port (port stripped - not supported yet)", | ||
| host: "https://ghes.local:8443", | ||
| expectedOAuth: "https://ghes.local/login/oauth", // Port is stripped ref: ln222 api.go comment |
There was a problem hiding this comment.
Same issue here: the "ref: ln222" line-number reference is brittle. Consider updating the comment to reference the parseAPIHost port-handling note without tying it to a specific line number.
| expectedOAuth: "http://ghes.local/login/oauth", // Port is stripped ref: ln222 api.go comment | |
| }, | |
| { | |
| name: "GHES with HTTPS and custom port (port stripped - not supported yet)", | |
| host: "https://ghes.local:8443", | |
| expectedOAuth: "https://ghes.local/login/oauth", // Port is stripped ref: ln222 api.go comment | |
| expectedOAuth: "http://ghes.local/login/oauth", // Port is stripped; see parseAPIHost port-handling note in api.go | |
| }, | |
| { | |
| name: "GHES with HTTPS and custom port (port stripped - not supported yet)", | |
| host: "https://ghes.local:8443", | |
| expectedOAuth: "https://ghes.local/login/oauth", // Port is stripped; see parseAPIHost port-handling note in api.go |
| // APIHost is the GitHub API host resolver that provides OAuth URL. | ||
| // If set, this takes precedence over AuthorizationServer. | ||
| APIHost utils.APIHostResolver | ||
|
|
||
| // AuthorizationServer is the OAuth authorization server URL. | ||
| // Defaults to GitHub's OAuth server if not specified. | ||
| // This field is ignored if APIHost is set. | ||
| AuthorizationServer string |
There was a problem hiding this comment.
oauth.Config depends on utils.APIHostResolver, which requires several unrelated URL methods that oauth doesn’t use. This increases coupling and is what forced a breaking interface change in utils. Consider changing Config.APIHost to a minimal local interface (e.g., OAuthURL(ctx) (*url.URL, error)) so callers can supply only what oauth needs and utils.APIHost can be adapted without expanding exported interfaces.
Summary
Conditionally set AuthorizationServer only when
--gh-hostis explicitly provided, ensuringgithub.comusers receive the correct default.Why
To ensure that OAuth protected resource metadata correctly exposes the authorization server endpoint for all clients, including those using
github.comwherecfg.Hostmight be empty. This conditional setting prevents an invalidAuthorizationServervalue and ensures proper discovery of authentication details.What changed
pkg/http/server.go:AuthorizationServeris now only overridden whencfg.Host != "". Otherwise, it falls back toDefaultAuthorizationServer(https://github.com/login/oauth).MCP impact
Prompts tested (tool changes only)
N/A
Security / limits
Tool renaming
Lint & tests
./script/lint./script/testDocs