Skip to content

Conversation

@snowykami
Copy link

@snowykami snowykami commented Dec 13, 2025

fix #994

Summary by CodeRabbit

  • New Features
    • Added an admin-protected, rate-limited backend OIDC discovery endpoint for fetching OpenID Connect metadata.
    • UI: New "Use backend proxy" checkbox in OIDC settings to choose backend- or frontend-based discovery (helps avoid CORS).
    • Settings now persist the proxy choice and automatically populate OIDC authorization, token, and user-info endpoints from discovery.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Dec 13, 2025

Walkthrough

Adds a backend OIDC discovery HTTP handler and route, and updates the frontend settings UI to add an oidc.use_backend_proxy toggle that, when enabled, fetches OIDC discovery via the backend proxy instead of the browser contacting the well-known URL directly.

Changes

Cohort / File(s) Summary
Backend OIDC Handler
controller/oidc.go
New handler GetOIDCDiscovery(c *gin.Context) that validates a well_known_url (must start with http:// or https://), enforces SSRF checks via system fetch settings, performs a GET with a 5s timeout, decodes JSON into a generic map, and returns { success: bool, data: <map> }. Handles missing/invalid URL, SSRF rejection, network/timeouts, and JSON parse errors.
API Route Setup
router/api-router.go
Registers route GET /api/oidc/discovery guarded by AdminAuth() and CriticalRateLimit(), routed to GetOIDCDiscovery.
Frontend OIDC Settings
web/src/components/settings/SystemSetting.jsx
Adds oidc.use_backend_proxy config (default true) and UI checkbox. When enabled, frontend requests discovery from /api/oidc/discovery and reads the backend response (response.data.data) to populate authorization_endpoint, token_endpoint, and user_info_endpoint. When disabled, falls back to direct fetch from the well-known URL. Persists oidc.use_backend_proxy when changed.

Sequence Diagram(s)

sequenceDiagram
    participant Browser as Frontend (Browser)
    participant Server as Backend API
    participant Provider as OIDC Provider (.well-known)

    Browser->>Server: GET /api/oidc/discovery?well_known_url=<url> (AdminAuth, rate-limited)
    Server->>Provider: GET <well_known_url> (5s timeout, SSRF validated)
    alt success & valid JSON
        Provider-->>Server: 200 + discovery JSON
        Server-->>Browser: 200 { success: true, data: <discovery JSON> }
    else invalid URL / SSRF / network / non-JSON
        Server-->>Browser: 4xx/5xx { success: false, message: <error> }
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20–30 minutes

  • Review focus:
    • controller/oidc.go — URL validation, SSRF check implementation, timeout behavior, and JSON response shape.
    • router/api-router.go — middleware order and route registration correctness.
    • web/src/components/settings/SystemSetting.jsx — correct path to backend result (response.data.data vs response.data), proper fallback to direct fetch, and persistence of oidc.use_backend_proxy.

Poem

🐰 I hopped from frontend to server with glee,
Fetching .well-known where browsers can't be,
A tiny proxy tunnel, safe and neat,
CORS left behind, discovery complete —
Carrot-coded joy in every heartbeat! 🥕

Pre-merge checks and finishing touches

❌ Failed checks (2 warnings)
Check name Status Explanation Resolution
Linked Issues check ⚠️ Warning The PR implements the core objective from issue #994 by moving OIDC discovery to backend with SSRF protection; however, CSRF protection concerns raised in comments are not addressed in the implementation. Add CSRF protection to the backend proxy endpoint and implement a configuration flag to control whether the proxy is enabled by default, as recommended in issue comments.
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main change: moving OIDC discovery URL fetching from frontend to backend proxy to resolve CORS issues.
Out of Scope Changes check ✅ Passed All changes are within scope: new backend handler for OIDC discovery, route setup with admin auth, and frontend UI to toggle the proxy feature align with the objective to move discovery to backend.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
web/src/components/settings/SystemSetting.jsx (1)

501-526: Guard on { success } and avoid mutating inputs directly in OIDC discovery flow.
If the backend returns { success:false, message } (possibly with HTTP 200), the current code will throw on res.data[...]. Also direct mutation can fail to re-render.

-        const res = await API.get('/api/oidc/discovery', {
+        const payload = await API.get('/api/oidc/discovery', {
           params: { well_known_url: inputs['oidc.well_known'] },
-        }).then((response => response.data));
-        inputs['oidc.authorization_endpoint'] =
-          res.data['authorization_endpoint'];
-        inputs['oidc.token_endpoint'] = res.data['token_endpoint'];
-        inputs['oidc.user_info_endpoint'] = res.data['userinfo_endpoint'];
+        }).then((response) => response.data);
+        if (!payload?.success) {
+          showError(payload?.message || t('获取 OIDC 配置失败'));
+          return;
+        }
+        const cfg = payload.data || {};
+        setInputs((prev) => ({
+          ...prev,
+          'oidc.authorization_endpoint': cfg['authorization_endpoint'] || '',
+          'oidc.token_endpoint': cfg['token_endpoint'] || '',
+          'oidc.user_info_endpoint': cfg['userinfo_endpoint'] || '',
+        }));
         showSuccess(t('获取 OIDC 配置成功!'));
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 689c431 and 1498b60.

📒 Files selected for processing (3)
  • controller/oidc.go (1 hunks)
  • router/api-router.go (1 hunks)
  • web/src/components/settings/SystemSetting.jsx (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
router/api-router.go (2)
middleware/rate-limit.go (1)
  • CriticalRateLimit (104-109)
controller/oidc.go (1)
  • GetOIDCDiscovery (230-278)
web/src/components/settings/SystemSetting.jsx (1)
web/src/helpers/api.js (6)
  • res (224-224)
  • res (225-225)
  • res (301-301)
  • res (302-302)
  • API (29-37)
  • API (29-37)

@seefs001
Copy link
Collaborator

看了下还是需要在请求的地方要加入csrf防护的拦截,这样的话就不能默认开启反代的方式了,需要加一个配置项允许切换,需要手动配置反代的地址到csrf名单那边。
也应该默认不开启,看前面的讨论cors按标准有些服务商是开放的

@snowykami
Copy link
Author

看了下还是需要在请求的地方要加入csrf防护的拦截,这样的话就不能默认开启反代的方式了,需要加一个配置项允许切换,需要手动配置反代的地址到csrf名单那边。 也应该默认不开启,看前面的讨论cors按标准有些服务商是开放的

OK,我忙完了看看怎么修改合适

@snowykami
Copy link
Author

已提交最新的SSRF修复补丁

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (1)
controller/oidc.go (1)

269-292: Add redirect blocking, status code validation, and response size limit for defense-in-depth.

The HTTP client follows redirects by default, allowing SSRF bypass via open redirects. Additionally, missing status code validation and unbounded response decoding create reliability and DoS risks.

🔎 Apply these hardening measures:
+	import "io"
+
 	// 请求OIDC发现端点
 	client := http.Client{
 		Timeout: 5 * time.Second,
+		CheckRedirect: func(req *http.Request, via []*http.Request) error {
+			return http.ErrUseLastResponse // Block redirects to prevent SSRF bypass
+		},
 	}

 	res, err := client.Get(wellKnownURL)
 	if err != nil {
 		c.JSON(http.StatusInternalServerError, gin.H{
 			"success": false,
 			"message": "获取 OIDC 配置失败,请检查网络状况和 Well-Known URL 是否正确",
 		})
 		return
 	}
 	defer res.Body.Close()

+	if res.StatusCode < 200 || res.StatusCode >= 300 {
+		c.JSON(http.StatusBadGateway, gin.H{
+			"success": false,
+			"message": fmt.Sprintf("OIDC 服务器返回错误状态码: %d", res.StatusCode),
+		})
+		return
+	}
+
 	var discoveryConfig map[string]interface{}
-	err = json.NewDecoder(res.Body).Decode(&discoveryConfig)
+	err = json.NewDecoder(io.LimitReader(res.Body, 1<<20)).Decode(&discoveryConfig) // 1MB limit
 	if err != nil {
🧹 Nitpick comments (1)
web/src/components/settings/SystemSetting.jsx (1)

513-527: Remove debug logging and consider immutable state update.

Line 524 has a console.log that should be removed. Also, directly mutating inputs (lines 525-527) can cause React re-render issues, though this pattern is used elsewhere in the file.

🔎 Suggested fix:
         res = await axios.create().get(inputs['oidc.well_known']).then(response => response.data);
       }
-      console.log(res);
-      inputs['oidc.authorization_endpoint'] = res['authorization_endpoint'];
-      inputs['oidc.token_endpoint'] = res['token_endpoint'];
-      inputs['oidc.user_info_endpoint'] = res['userinfo_endpoint'];
+      setInputs(prev => ({
+        ...prev,
+        'oidc.authorization_endpoint': res['authorization_endpoint'],
+        'oidc.token_endpoint': res['token_endpoint'],
+        'oidc.user_info_endpoint': res['userinfo_endpoint'],
+      }));
       showSuccess(t('获取 OIDC 配置成功!'));
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 1498b60 and 3a29e0f.

📒 Files selected for processing (3)
  • controller/oidc.go (1 hunks)
  • router/api-router.go (1 hunks)
  • web/src/components/settings/SystemSetting.jsx (5 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • router/api-router.go
🧰 Additional context used
🧬 Code graph analysis (2)
web/src/components/settings/SystemSetting.jsx (1)
web/src/helpers/api.js (6)
  • res (224-224)
  • res (225-225)
  • res (301-301)
  • res (302-302)
  • API (29-37)
  • API (29-37)
controller/oidc.go (2)
setting/system_setting/fetch_setting.go (1)
  • GetFetchSetting (32-34)
common/ssrf_protection.go (1)
  • ValidateURLWithFetchSetting (305-327)
🔇 Additional comments (4)
web/src/components/settings/SystemSetting.jsx (4)

65-65: LGTM! Sensible default of true for oidc.use_backend_proxy to avoid CORS issues by default.


188-188: LGTM! Correct boolean parsing for the new setting.


582-589: LGTM! Correctly saves the backend proxy setting only when changed.


1375-1390: LGTM! UI implementation follows the existing pattern for checkbox settings. The checkbox correctly integrates with the form and uses the standard handleCheckboxChange handler.

Comment on lines 248 to 267
fetchSetting := system_setting.GetFetchSetting()
// SSRF防护 - 验证URL安全性
err := common.ValidateURLWithFetchSetting(
wellKnownURL,
true,
true,
false,
false,
fetchSetting.DomainList,
fetchSetting.IpList,
fetchSetting.AllowedPorts,
true,
)
if err != nil {
c.JSON(http.StatusForbidden, gin.H{
"success": false,
"message": fmt.Sprintf("URL SSRF安全验证失败: %s", err.Error()),
})
return
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

SSRF protection is ineffective due to allowPrivateIp=true.

The second parameter (true on line 253) allows requests to private IP addresses, which defeats the primary purpose of SSRF protection. This permits attackers to target internal services at 127.0.0.1, 192.168.x.x, and cloud metadata endpoints like 169.254.169.254.

🔎 Apply this diff to block private IP access:
 	fetchSetting := system_setting.GetFetchSetting()
 	// SSRF防护 - 验证URL安全性
 	err := common.ValidateURLWithFetchSetting(
 		wellKnownURL,
 		true,
-		true,
+		fetchSetting.AllowPrivateIp, // Use configured setting, defaults to false for security
 		false,
 		false,
 		fetchSetting.DomainList,
 		fetchSetting.IpList,
 		fetchSetting.AllowedPorts,
 		true,
 	)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
fetchSetting := system_setting.GetFetchSetting()
// SSRF防护 - 验证URL安全性
err := common.ValidateURLWithFetchSetting(
wellKnownURL,
true,
true,
false,
false,
fetchSetting.DomainList,
fetchSetting.IpList,
fetchSetting.AllowedPorts,
true,
)
if err != nil {
c.JSON(http.StatusForbidden, gin.H{
"success": false,
"message": fmt.Sprintf("URL SSRF安全验证失败: %s", err.Error()),
})
return
}
fetchSetting := system_setting.GetFetchSetting()
// SSRF防护 - 验证URL安全性
err := common.ValidateURLWithFetchSetting(
wellKnownURL,
true,
fetchSetting.AllowPrivateIp, // Use configured setting, defaults to false for security
false,
false,
fetchSetting.DomainList,
fetchSetting.IpList,
fetchSetting.AllowedPorts,
true,
)
if err != nil {
c.JSON(http.StatusForbidden, gin.H{
"success": false,
"message": fmt.Sprintf("URL SSRF安全验证失败: %s", err.Error()),
})
return
}
🤖 Prompt for AI Agents
In controller/oidc.go around lines 248 to 267, the SSRF check currently calls
ValidateURLWithFetchSetting with the second boolean set to true
(allowPrivateIp=true), which permits private IPs and defeats SSRF protection;
change that argument to false so private IPs are blocked, keep the other
parameters as-is, and ensure any callers or unit tests that rely on allowing
private IPs are updated accordingly.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (1)
controller/oidc.go (1)

284-292: Limit response body size to prevent DoS attacks.

The response body is decoded without size limits, allowing malicious or misconfigured OIDC servers to exhaust server memory by sending unbounded response sizes.

🔎 Apply this diff to add 1MB response size limit
+	// 限制响应体大小防止DoS
+	limitedReader := io.LimitReader(res.Body, 1<<20) // 1MB limit
+
 	var discoveryConfig map[string]interface{}
-	err = json.NewDecoder(res.Body).Decode(&discoveryConfig)
+	err = json.NewDecoder(limitedReader).Decode(&discoveryConfig)
 	if err != nil {
 		c.JSON(http.StatusInternalServerError, gin.H{
 			"success": false,
 			"message": "解析 OIDC 配置失败",
 		})
 		return
 	}

Note: You'll need to add "io" to the imports at the top of the file.

Based on past review comments noting this pattern for similar endpoints.

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3a29e0f and c37a8e9.

📒 Files selected for processing (1)
  • controller/oidc.go (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
controller/oidc.go (2)
setting/system_setting/fetch_setting.go (1)
  • GetFetchSetting (32-34)
common/ssrf_protection.go (1)
  • ValidateURLWithFetchSetting (305-327)
🔇 Additional comments (3)
controller/oidc.go (3)

230-238: LGTM!

Parameter validation correctly ensures the required well_known_url is provided.


240-247: LGTM!

URL scheme validation correctly restricts input to HTTP/HTTPS protocols.


248-267: LGTM!

SSRF protection is correctly implemented using system fetch settings. The previous concern about hardcoded allowPrivateIp=true has been resolved—the code now properly uses fetchSetting.AllowPrivateIp to respect system configuration.

Comment on lines +269 to +282
// 请求OIDC发现端点
client := http.Client{
Timeout: 5 * time.Second,
}

res, err := client.Get(wellKnownURL)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{
"success": false,
"message": "获取 OIDC 配置失败,请检查网络状况和 Well-Known URL 是否正确",
})
return
}
defer res.Body.Close()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Add CheckRedirect and validate HTTP status code before decoding.

Two critical hardening measures are missing:

  1. Missing CheckRedirect: The HTTP client follows redirects by default, allowing attackers to bypass SSRF validation by redirecting from an allowed URL to internal endpoints (e.g., 169.254.169.254). Past review comments explicitly noted this pattern.

  2. Missing status code validation: The code attempts to decode the response regardless of HTTP status (4xx/5xx), which leads to confusing error messages and potential JSON parsing failures on error pages.

🔎 Apply this diff to add both protections
 	// 请求OIDC发现端点
 	client := http.Client{
 		Timeout: 5 * time.Second,
+		CheckRedirect: func(req *http.Request, via []*http.Request) error {
+			return http.ErrUseLastResponse
+		},
 	}

 	res, err := client.Get(wellKnownURL)
 	if err != nil {
 		c.JSON(http.StatusInternalServerError, gin.H{
 			"success": false,
 			"message": "获取 OIDC 配置失败,请检查网络状况和 Well-Known URL 是否正确",
 		})
 		return
 	}
 	defer res.Body.Close()
+
+	if res.StatusCode != http.StatusOK {
+		c.JSON(http.StatusInternalServerError, gin.H{
+			"success": false,
+			"message": fmt.Sprintf("OIDC 服务器返回错误状态码: %d", res.StatusCode),
+		})
+		return
+	}

Reference implementations in the codebase:

  • CheckRedirect: service/http_client.go:44-52
  • Status code checks: service/webhook.go:87-88, service/download.go:61-66
🤖 Prompt for AI Agents
In controller/oidc.go around lines 269-282, the HTTP client used to fetch the
OIDC well-known URL lacks a CheckRedirect policy and does not validate the
response status code before decoding; update the client construction to set
CheckRedirect to a safe policy (reject or limit redirects consistent with
service/http_client.go) and keep the Timeout, then after
client.Get(wellKnownURL) check the returned error and also verify res.StatusCode
is in the 2xx range (return a 4xx/5xx appropriate JSON error if not) before
attempting to read or decode res.Body; ensure res.Body is closed in all
early-return paths.

@seefs001 seefs001 added the Ready to merge It will eventually merge, requiring a final check. label Dec 26, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Ready to merge It will eventually merge, requiring a final check.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

将 OIDC 服务发现移动到后端

2 participants