Skip to content

Commit 66d4a43

Browse files
fix(oauth): address code review feedback - add timeout, clarify comments, clean tests
1 parent eeace35 commit 66d4a43

3 files changed

Lines changed: 141 additions & 20 deletions

File tree

Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
# Code Review: PR #188 - Auto-detect RFC 8707 Resource Parameter for OAuth
2+
3+
**Date**: 2025-12-10
4+
**PR**: https://github.com/smart-mcp-proxy/mcpproxy-go/pull/188
5+
**Reviewer**: Claude Code
6+
7+
## Overview
8+
9+
This PR implements automatic detection of the RFC 8707 `resource` parameter from RFC 9728 Protected Resource Metadata, enabling zero-configuration OAuth for providers like Runlayer/AnySource. This is a well-structured feature addition that improves user experience by eliminating manual `extra_params` configuration.
10+
11+
## Summary of Changes
12+
13+
| File | Type | Description |
14+
|------|------|-------------|
15+
| `internal/oauth/config.go` | New API | Added `CreateOAuthConfigWithExtraParams()` and `autoDetectResource()` |
16+
| `internal/oauth/discovery.go` | Enhancement | Added `DiscoverProtectedResourceMetadata()` |
17+
| `internal/upstream/core/connection.go` | Integration | Updated 4 call sites to use new API |
18+
| `cmd/mcpproxy/auth_cmd.go` | UX | Added resource status display |
19+
| `internal/management/diagnostics.go` | UX | Updated doctor resolution message |
20+
| `CLAUDE.md` | Docs | Comprehensive documentation |
21+
| Tests | Verification | Extensive unit and E2E tests |
22+
23+
---
24+
25+
## Positives
26+
27+
### 1. Clean API Design
28+
The new `CreateOAuthConfigWithExtraParams()` function returns both the config and extraParams, enabling the connection layer to inject params into the authorization URL. The backward-compatible `CreateOAuthConfig()` is preserved for existing callers.
29+
30+
### 2. Proper Priority Order
31+
```go
32+
// Resource auto-detection logic (in priority order):
33+
// 1. Manual extra_params.resource from config (highest priority)
34+
// 2. Auto-detected resource from RFC 9728 Protected Resource Metadata
35+
// 3. Fallback to server URL if metadata unavailable
36+
```
37+
38+
This correctly preserves backward compatibility - manual overrides always win.
39+
40+
### 3. Excellent Test Coverage
41+
- Unit tests for `DiscoverProtectedResourceMetadata()` with error cases
42+
- Unit tests for resource auto-detection and manual override
43+
- E2E test validating full metadata discovery flow
44+
- Tests for parameter merging behavior
45+
46+
### 4. Good Documentation
47+
- CLAUDE.md updated with clear explanation and examples
48+
- Inline code comments explain RFC references
49+
- Diagnostic commands documented for troubleshooting
50+
51+
---
52+
53+
## Issues and Suggestions
54+
55+
### 1. **Network Request in Config Creation** (Medium Risk) - FIXED
56+
57+
```go:internal/oauth/config.go
58+
func autoDetectResource(serverConfig *config.ServerConfig, logger *zap.Logger) string {
59+
resp, err := http.Post(serverConfig.URL, "application/json", strings.NewReader("{}"))
60+
```
61+
62+
**Concern**: The `CreateOAuthConfigWithExtraParams()` function makes a synchronous HTTP POST request during config creation. This can cause:
63+
- Slowdown during startup if the server is slow/unreachable
64+
- Potential timeout issues affecting connection initialization
65+
- No timeout specified on the `http.Post` call
66+
67+
**Fix**: Added 5-second timeout to preflight request client.
68+
69+
### 2. **Fallback Logic Inconsistency** (Low Risk) - FIXED
70+
71+
In `autoDetectResource()`:
72+
- On network error: returns `serverConfig.URL` (fallback)
73+
- On non-401 response: returns `""` (no resource)
74+
- On 401 without metadata URL: returns `serverConfig.URL` (fallback)
75+
76+
The non-401 case returning empty string seems correct (server doesn't require OAuth), but the behavior isn't immediately obvious from the code.
77+
78+
**Fix**: Added clarifying comment explaining why non-401 returns empty.
79+
80+
### 3. **Unused Variable in E2E Test** - FIXED
81+
82+
```go:internal/server/e2e_oauth_zero_config_test.go
83+
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
84+
defer cancel()
85+
```
86+
87+
The `ctx` variable is created but never passed to the discovery function.
88+
89+
**Fix**: Removed unused context variable and related check.
90+
91+
### 4. **Minor: Unused import check** - FIXED
92+
93+
```go:internal/server/e2e_oauth_zero_config_test.go
94+
// compile-time check to silence unused import warning
95+
var _ = zap.NewNop
96+
```
97+
98+
**Fix**: Removed unused zap import and dummy reference.
99+
100+
---
101+
102+
## Security Considerations
103+
104+
**Positive**: The implementation correctly:
105+
- Respects manual configuration over auto-detected values
106+
- Masks sensitive params in logs (existing `maskExtraParams` function)
107+
- Uses standard HTTP methods for discovery
108+
109+
**Note**: The POST request with empty body `{}` to detect 401 is per MCP spec. Consider documenting this as it might appear suspicious in network logs.
110+
111+
---
112+
113+
## Performance Considerations
114+
115+
The preflight POST request adds latency to connection establishment. For servers that don't advertise RFC 9728 metadata, this is overhead with no benefit. Consider:
116+
- Caching metadata discovery results
117+
- Making discovery async (non-blocking)
118+
119+
However, this is likely acceptable for v1 since it only happens during initial connection.
120+
121+
---
122+
123+
## Verdict
124+
125+
**Approved with minor fixes applied.**
126+
127+
This is a well-implemented feature with good test coverage and documentation. The code follows project conventions and the API design is clean.
128+
129+
### Fixes Applied:
130+
1. Added timeout to `http.Post` call in `autoDetectResource()`
131+
2. Added clarifying comment for non-401 return behavior
132+
3. Removed unused `ctx` variable in E2E test
133+
4. Removed unused zap import workaround in E2E test

internal/oauth/config.go

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -357,8 +357,11 @@ func CreateOAuthConfigWithExtraParams(serverConfig *config.ServerConfig, storage
357357
// autoDetectResource attempts to discover the RFC 8707 resource parameter.
358358
// Returns the detected resource URL, or server URL as fallback, or empty string on failure.
359359
func autoDetectResource(serverConfig *config.ServerConfig, logger *zap.Logger) string {
360+
// Use a client with timeout to avoid blocking on slow/unreachable servers
361+
client := &http.Client{Timeout: 5 * time.Second}
362+
360363
// POST is the only method guaranteed by MCP spec for the main endpoint
361-
resp, err := http.Post(serverConfig.URL, "application/json", strings.NewReader("{}"))
364+
resp, err := client.Post(serverConfig.URL, "application/json", strings.NewReader("{}"))
362365
if err != nil {
363366
logger.Debug("Failed to make preflight request for resource detection",
364367
zap.String("server", serverConfig.Name),
@@ -407,8 +410,9 @@ func autoDetectResource(serverConfig *config.ServerConfig, logger *zap.Logger) s
407410
return serverConfig.URL
408411
}
409412

410-
// Non-401 response - server might not require OAuth or is accessible
411-
// Don't set resource parameter in this case
413+
// Non-401 response means server doesn't require authentication at this endpoint.
414+
// Return empty string to avoid adding unnecessary resource parameter to OAuth flows.
415+
// This is correct behavior: resource parameter is only needed for OAuth-protected servers.
412416
logger.Debug("Server did not return 401, skipping resource auto-detection",
413417
zap.String("server", serverConfig.Name),
414418
zap.Int("status_code", resp.StatusCode))

internal/server/e2e_oauth_zero_config_test.go

Lines changed: 1 addition & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
package server
22

33
import (
4-
"context"
54
"encoding/json"
65
"net/http"
76
"net/http/httptest"
@@ -17,9 +16,6 @@ import (
1716
"go.uber.org/zap"
1817
)
1918

20-
// compile-time check to silence unused import warning
21-
var _ = zap.NewNop
22-
2319
// TestE2E_ZeroConfigOAuth_ResourceParameterExtraction validates that the system
2420
// correctly extracts resource parameters from Protected Resource Metadata (RFC 9728)
2521
// when no explicit OAuth configuration is provided.
@@ -213,11 +209,7 @@ func TestE2E_ProtectedResourceMetadataDiscovery(t *testing.T) {
213209
}))
214210
defer metadataServer.Close()
215211

216-
// Test direct metadata discovery
217-
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
218-
defer cancel()
219-
220-
// Use the oauth package's discovery function
212+
// Test direct metadata discovery using the oauth package's discovery function
221213
metadata, err := oauth.DiscoverProtectedResourceMetadata(metadataServer.URL, 5*time.Second)
222214

223215
require.NoError(t, err, "Metadata discovery should succeed")
@@ -235,12 +227,4 @@ func TestE2E_ProtectedResourceMetadataDiscovery(t *testing.T) {
235227
t.Logf(" Resource: %s", metadata.Resource)
236228
t.Logf(" Scopes: %v", metadata.ScopesSupported)
237229
t.Logf(" Auth Servers: %v", metadata.AuthorizationServers)
238-
239-
// Ensure context wasn't cancelled
240-
select {
241-
case <-ctx.Done():
242-
t.Fatal("Context should not be cancelled")
243-
default:
244-
// Context is still active, good
245-
}
246230
}

0 commit comments

Comments
 (0)