diff --git a/pkg/auth/oauth.go b/pkg/auth/oauth.go index ebf6a9f3..ae599c85 100644 --- a/pkg/auth/oauth.go +++ b/pkg/auth/oauth.go @@ -4,6 +4,7 @@ import ( "context" "crypto/rand" "crypto/sha256" + "crypto/tls" _ "embed" "encoding/base64" "encoding/json" @@ -45,6 +46,8 @@ const ( // OAuth scopes - openid for the MCP server flow DefaultScope = "openid email" + + oauthTokenRequestTimeout = 30 * time.Second ) // OAuthConfig represents the OAuth2 configuration @@ -311,7 +314,11 @@ func (oc *OAuthConfig) exchangeCodeForTokens(ctx context.Context, code, orgID st opts = append(opts, oauth2.SetAuthURLParam("org_id", orgID)) } - token, err := oc.Config.Exchange(ctx, code, opts...) + exchangeCtx, cancel := context.WithTimeout(ctx, oauthTokenRequestTimeout) + defer cancel() + exchangeCtx = context.WithValue(exchangeCtx, oauth2.HTTPClient, newOAuthHTTPClient()) + + token, err := oc.Config.Exchange(exchangeCtx, code, opts...) if err != nil { return nil, fmt.Errorf("failed to exchange code for token: %w", err) } @@ -326,6 +333,17 @@ func (oc *OAuthConfig) exchangeCodeForTokens(ctx context.Context, code, orgID st }, nil } +func newOAuthHTTPClient() *http.Client { + transport := http.DefaultTransport.(*http.Transport).Clone() + // Keep token requests on HTTP/1.1; the auth endpoint can leave HTTP/2 requests pending. + transport.TLSNextProto = map[string]func(string, *tls.Conn) http.RoundTripper{} + + return &http.Client{ + Transport: transport, + Timeout: oauthTokenRequestTimeout, + } +} + // RefreshTokens refreshes the access token using the refresh token func RefreshTokens(ctx context.Context, tokens *TokenStorage) (*TokenStorage, error) { if tokens.RefreshToken == "" { @@ -349,7 +367,7 @@ func RefreshTokens(ctx context.Context, tokens *TokenStorage) (*TokenStorage, er req.Header.Set("Content-Type", "application/x-www-form-urlencoded") - client := &http.Client{} + client := newOAuthHTTPClient() resp, err := client.Do(req) if err != nil { return nil, fmt.Errorf("failed to send refresh request: %w", err) diff --git a/pkg/auth/oauth_test.go b/pkg/auth/oauth_test.go index 059034e0..ba664a7e 100644 --- a/pkg/auth/oauth_test.go +++ b/pkg/auth/oauth_test.go @@ -1,6 +1,9 @@ package auth -import "testing" +import ( + "net/http" + "testing" +) func TestNewOAuthConfigUsesAuthOverrides(t *testing.T) { t.Setenv("KERNEL_AUTH_BASE_URL", "https://auth.dev.onkernel.com/") @@ -55,3 +58,21 @@ func TestLegacyTokenRefreshConfigUsesProdDefaults(t *testing.T) { t.Fatalf("tokenOAuthClientID = %q, want %q", got, want) } } + +func TestOAuthHTTPClientUsesHTTP11WithTimeout(t *testing.T) { + client := newOAuthHTTPClient() + if got, want := client.Timeout, oauthTokenRequestTimeout; got != want { + t.Fatalf("client timeout = %s, want %s", got, want) + } + + transport, ok := client.Transport.(*http.Transport) + if !ok { + t.Fatalf("client transport type = %T, want *http.Transport", client.Transport) + } + if transport.TLSNextProto == nil { + t.Fatal("TLSNextProto is nil, want HTTP/2 disabled explicitly") + } + if _, ok := transport.TLSNextProto["h2"]; ok { + t.Fatal("TLSNextProto enables HTTP/2") + } +} diff --git a/pkg/auth/success.html b/pkg/auth/success.html index 1fb33602..4b093843 100644 --- a/pkg/auth/success.html +++ b/pkg/auth/success.html @@ -1,7 +1,7 @@
-