Skip to content

Bug fix oauth redirect issue#4954

Open
dingjianrui-crypto wants to merge 1 commit into
QuantumNous:mainfrom
dingjianrui-crypto:fix/oauth
Open

Bug fix oauth redirect issue#4954
dingjianrui-crypto wants to merge 1 commit into
QuantumNous:mainfrom
dingjianrui-crypto:fix/oauth

Conversation

@dingjianrui-crypto
Copy link
Copy Markdown

@dingjianrui-crypto dingjianrui-crypto commented May 19, 2026

⚠️ 提交说明 / PR Notice

Important

  • 请提供人工撰写的简洁摘要,避免直接粘贴未经整理的 AI 输出。

📝 变更描述 / Description

(简述:做了什么?为什么这样改能生效?请基于你对代码逻辑的理解来写,避免粘贴未经整理的内容)

🚀 变更类型 / Type of change

  • 🐛 Bug 修复 (Bug fix) - 请关联对应 Issue,避免将设计取舍、理解偏差或预期不一致直接归类为 bug
  • ✨ 新功能 (New feature) - 重大特性建议先通过 Issue 沟通
  • ⚡ 性能优化 / 重构 (Refactor)
  • 📝 文档更新 (Documentation)

🔗 关联任务 / Related Issue

  • Closes # (如有)

✅ 提交前检查项 / Checklist

  • 人工确认: 我已亲自整理并撰写此描述,没有直接粘贴未经处理的 AI 输出。
  • 非重复提交: 我已搜索现有的 IssuesPRs,确认不是重复提交。
  • Bug fix 说明: 若此 PR 标记为 Bug fix,我已提交或关联对应 Issue,且不会将设计取舍、预期不一致或理解偏差直接归类为 bug。
  • 变更理解: 我已理解这些更改的工作原理及可能影响。
  • 范围聚焦: 本 PR 未包含任何与当前任务无关的代码改动。
  • 本地验证: 已在本地运行并通过测试或手动验证,维护者可以据此复核结果。
  • 安全合规: 代码中无敏感凭据,且符合项目代码规范。

📸 运行证明 / Proof of Work

(请在此粘贴截图、关键日志或测试报告,以证明变更生效)
image

Summary by CodeRabbit

Release Notes

  • Bug Fixes

    • Improved OAuth redirect URI computation to properly respect proxy headers and request context for better compatibility with reverse proxy setups.
  • Tests

    • Added tests for OAuth redirect URI handling in various network configurations.

Review Change Stack

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 19, 2026

Walkthrough

This PR refactors OAuth redirect URI construction to derive the public origin from incoming request headers instead of relying solely on static server configuration. Three OAuth providers (Discord, Generic, OIDC) now use a new callbackRedirectURI helper that prioritizes forwarded headers and request state with fallback to system settings. A separate frontend change clears the uid localStorage entry during auth reset.

Changes

OAuth Redirect URI Refactoring

Layer / File(s) Summary
Redirect URI construction from request headers
oauth/redirect.go
New module derives public origin from X-Forwarded-Proto/Host, Forwarded header, TLS state, and request URL. callbackRedirectURI builds callback URLs by combining computed origin with a provided path, falling back to system_setting.ServerAddress. Helper functions normalize header parsing.
Redirect URI logic tests
oauth/redirect_test.go
Two tests assert callbackRedirectURI correctly derives origin from forwarded headers when present and from request properties when absent.
OAuth provider redirect URI integration
oauth/discord.go, oauth/generic.go, oauth/oidc.go
Discord, Generic, and OIDC providers now call callbackRedirectURI(c, ...) for token exchange redirect_uri computation. Generic provider's unused system_setting import is removed.

Auth Store localStorage Cleanup

Layer / File(s) Summary
Auth store uid cleanup
web/default/src/stores/auth-store.ts
auth.reset() now removes both user and uid entries from localStorage, expanding persisted state cleanup on logout.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~22 minutes

Poem

🐰 Forwarded headers now guide the way,
No static address leads astray,
Request-aware redirects take their flight,
While localStorage bids uid goodnight!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Bug fix oauth redirect issue' is directly related to the main changes in the changeset, which refactor OAuth redirect URI construction across multiple OAuth providers.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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
Copy Markdown
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

🧹 Nitpick comments (1)
oauth/redirect_test.go (1)

11-26: ⚡ Quick win

Add coverage for Forwarded-header host fallback.

Current tests don’t cover the Forwarded: proto=...;host=... path, which is a key reverse-proxy branch in requestPublicOrigin.

🧪 Suggested test case
 func TestCallbackRedirectURIUsesRequestOrigin(t *testing.T) {
@@
 	require.Equal(t, "https://www.modelsphere.net/oauth/google", callbackRedirectURI(c, "/oauth/google"))
 }
+
+func TestCallbackRedirectURIUsesForwardedHeaderHostAndProto(t *testing.T) {
+	req := httptest.NewRequest("GET", "http://internal:3000/api/oauth/google", nil)
+	req.Header.Set("Forwarded", `for=1.2.3.4;proto=https;host=www.modelsphere.net`)
+
+	c := &gin.Context{Request: req}
+
+	require.Equal(t, "https://www.modelsphere.net/oauth/google", callbackRedirectURI(c, "/oauth/google"))
+}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@oauth/redirect_test.go` around lines 11 - 26, Add a test that covers the
Forwarded-header fallback in requestPublicOrigin: create an httptest request
with an internal URL (e.g., http://internal:3000/api/oauth/google), set the
Forwarded header to include proto and host (e.g.,
"proto=https;host=www.modelsphere.net"), build a gin.Context with that request
and assert callbackRedirectURI(c, "/oauth/google") returns
"https://www.modelsphere.net/oauth/google"; this ensures callbackRedirectURI and
requestPublicOrigin correctly parse the Forwarded header host fallback.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@oauth/redirect.go`:
- Around line 24-35: The code that determines host uses
firstForwardedValue(req.Header.Get("X-Forwarded-Host")) and falls back to
req.Host, but it doesn't parse the host from the standard Forwarded header;
update the host resolution to also extract host from req.Header.Get("Forwarded")
when X-Forwarded-Host is empty by reusing or adding a parser (similar to
forwardedProto) to pull the host=... token (e.g., call
forwardedHost(forwardedHeader) or extend firstForwardedValue to check the
Forwarded header), then assign that parsed host to the host variable before
falling back to req.Host so redirect_uri uses the correct proxy-provided host.

In `@web/default/src/stores/auth-store.ts`:
- Line 97: initUser currently restores only the persisted user but not the uid
that reset() clears; update the initUser initialization to also read
window.localStorage.getItem('uid') alongside 'user' and return/assign the uid
into the store (same place where the user is restored), and in the catch block
ensure you remove both 'user' and 'uid' from localStorage; reference the
initUser initializer and the reset() method and the keys 'user' and 'uid' so the
OAuth-set uid (web/default/src/routes/oauth/$provider.tsx) is available for the
API module (web/default/src/lib/api.ts) after page reloads.

---

Nitpick comments:
In `@oauth/redirect_test.go`:
- Around line 11-26: Add a test that covers the Forwarded-header fallback in
requestPublicOrigin: create an httptest request with an internal URL (e.g.,
http://internal:3000/api/oauth/google), set the Forwarded header to include
proto and host (e.g., "proto=https;host=www.modelsphere.net"), build a
gin.Context with that request and assert callbackRedirectURI(c, "/oauth/google")
returns "https://www.modelsphere.net/oauth/google"; this ensures
callbackRedirectURI and requestPublicOrigin correctly parse the Forwarded header
host fallback.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 3e1445ca-7930-4dce-a6cf-cd6401d36297

📥 Commits

Reviewing files that changed from the base of the PR and between 5dd0d3b and 2ee8e19.

📒 Files selected for processing (6)
  • oauth/discord.go
  • oauth/generic.go
  • oauth/oidc.go
  • oauth/redirect.go
  • oauth/redirect_test.go
  • web/default/src/stores/auth-store.ts

Comment thread oauth/redirect.go
Comment on lines +24 to +35
host := firstForwardedValue(req.Header.Get("X-Forwarded-Host"))
if host == "" {
host = strings.TrimSpace(req.Host)
}
if host == "" {
return ""
}

proto := firstForwardedValue(req.Header.Get("X-Forwarded-Proto"))
if proto == "" {
proto = forwardedProto(req.Header.Get("Forwarded"))
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Parse host= from Forwarded when X-Forwarded-Host is absent.

Line 24 only checks X-Forwarded-Host; if a proxy provides only Forwarded: proto=...;host=..., Line 26 falls back to internal req.Host, which can cause OAuth redirect_uri mismatch and failed token exchange.

💡 Suggested fix
 func requestPublicOrigin(c *gin.Context) string {
@@
 	req := c.Request
 	host := firstForwardedValue(req.Header.Get("X-Forwarded-Host"))
+	if host == "" {
+		host = forwardedHost(req.Header.Get("Forwarded"))
+	}
 	if host == "" {
 		host = strings.TrimSpace(req.Host)
 	}
@@
 	return strings.TrimSpace(proto) + "://" + host
 }
@@
 func forwardedProto(value string) string {
@@
 }
+
+func forwardedHost(value string) string {
+	value = firstForwardedValue(value)
+	if value == "" {
+		return ""
+	}
+	for _, part := range strings.Split(value, ";") {
+		part = strings.TrimSpace(part)
+		key, rawValue, ok := strings.Cut(part, "=")
+		if !ok || !strings.EqualFold(strings.TrimSpace(key), "host") {
+			continue
+		}
+		return strings.Trim(strings.TrimSpace(rawValue), `"`)
+	}
+	return ""
+}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@oauth/redirect.go` around lines 24 - 35, The code that determines host uses
firstForwardedValue(req.Header.Get("X-Forwarded-Host")) and falls back to
req.Host, but it doesn't parse the host from the standard Forwarded header;
update the host resolution to also extract host from req.Header.Get("Forwarded")
when X-Forwarded-Host is empty by reusing or adding a parser (similar to
forwardedProto) to pull the host=... token (e.g., call
forwardedHost(forwardedHeader) or extend firstForwardedValue to check the
Forwarded header), then assign that parsed host to the host variable before
falling back to req.Host so redirect_uri uses the correct proxy-provided host.

set((state) => {
if (typeof window !== 'undefined') {
window.localStorage.removeItem('user')
window.localStorage.removeItem('uid')
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Description: Find where uid is written to and read from localStorage to determine if it should be restored

# Search for localStorage.setItem('uid') or localStorage.getItem('uid')
echo "=== Searching for uid localStorage writes ==="
rg -n --type=ts --type=tsx -C3 "localStorage\.setItem\(['\"]uid['\"]"

echo -e "\n=== Searching for uid localStorage reads ==="
rg -n --type=ts --type=tsx -C3 "localStorage\.getItem\(['\"]uid['\"]"

echo -e "\n=== Searching for other uid localStorage operations ==="
rg -n --type=ts --type=tsx -C3 "localStorage\.(removeItem|clear).*uid"

Repository: QuantumNous/new-api

Length of output: 314


🏁 Script executed:

#!/bin/bash
# Re-run search with corrected syntax (remove problematic tsx type)
echo "=== Searching for uid localStorage writes ==="
rg -n "localStorage\.setItem\(['\"]uid['\"]" --max-count=20

echo -e "\n=== Searching for uid localStorage reads ==="
rg -n "localStorage\.getItem\(['\"]uid['\"]" --max-count=20

echo -e "\n=== Searching for alternative localStorage patterns for uid ==="
rg -n "localStorage\[.uid" --max-count=20

echo -e "\n=== Broader search for 'uid' in localStorage context ==="
rg -n "localStorage.*uid" -A2 -B2 --max-count=30

Repository: QuantumNous/new-api

Length of output: 2512


🏁 Script executed:

#!/bin/bash
# Read the full auth-store.ts file to understand context
echo "=== Full auth-store.ts file ==="
cat -n web/default/src/stores/auth-store.ts

Repository: QuantumNous/new-api

Length of output: 3688


Add uid restoration during initialization to match the reset cleanup.

The change removes uid from localStorage in the reset() method, but the uid value is not restored during initialization in initUser(). This creates an asymmetry: user is initialized, persisted, and cleared (full lifecycle), while uid is only cleared.

Based on the codebase search, uid is set by the OAuth flow (web/default/src/routes/oauth/$provider.tsx) and read by the API module (web/default/src/lib/api.ts) on every request. Since the reset() method now claims responsibility for clearing this state, the initUser() function should also restore it to maintain a complete lifecycle consistent with the coding guideline: "Persist state to localStorage within the store and restore it during initialization."

Consider restoring uid during initialization alongside user:

Suggested approach
const initUser = (() => {
  try {
    if (typeof window !== 'undefined') {
      const saved = window.localStorage.getItem('user')
      // Also check uid to ensure it's available after page reload
      window.localStorage.getItem('uid')
      return saved ? JSON.parse(saved) : null
    }
  } catch {
    if (typeof window !== 'undefined') {
      window.localStorage.removeItem('user')
      window.localStorage.removeItem('uid')
    }
  }
  return null
})()
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web/default/src/stores/auth-store.ts` at line 97, initUser currently restores
only the persisted user but not the uid that reset() clears; update the initUser
initialization to also read window.localStorage.getItem('uid') alongside 'user'
and return/assign the uid into the store (same place where the user is
restored), and in the catch block ensure you remove both 'user' and 'uid' from
localStorage; reference the initUser initializer and the reset() method and the
keys 'user' and 'uid' so the OAuth-set uid
(web/default/src/routes/oauth/$provider.tsx) is available for the API module
(web/default/src/lib/api.ts) after page reloads.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant