Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 38 additions & 18 deletions web/src/helpers/api.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,20 @@ export let API = axios.create({
},
});


function redirectToOAuthUrl(url, options = {}) {
const { openInNewTab = false } = options;
const targetUrl = typeof url === 'string' ? url : url.toString();

if (openInNewTab) {
window.open(targetUrl, '_blank');
return;
}

window.location.assign(targetUrl);
}


function patchAPIInstance(instance) {
const originalGet = instance.get.bind(instance);
const inFlightGetRequests = new Map();
Expand Down Expand Up @@ -249,7 +263,7 @@ export async function onDiscordOAuthClicked(client_id, options = {}) {
const redirect_uri = `${window.location.origin}/oauth/discord`;
const response_type = 'code';
const scope = 'identify+openid';
window.open(
redirectToOAuthUrl(
`https://discord.com/oauth2/authorize?client_id=${client_id}&redirect_uri=${redirect_uri}&response_type=${response_type}&scope=${scope}&state=${state}`,
);
}
Expand All @@ -268,17 +282,13 @@ export async function onOIDCClicked(
url.searchParams.set('response_type', 'code');
url.searchParams.set('scope', 'openid profile email');
url.searchParams.set('state', state);
if (openInNewTab) {
window.open(url.toString(), '_blank');
} else {
window.location.href = url.toString();
}
redirectToOAuthUrl(url, { openInNewTab });
}

export async function onGitHubOAuthClicked(github_client_id, options = {}) {
const state = await prepareOAuthState(options);
if (!state) return;
window.open(
redirectToOAuthUrl(
`https://github.com/login/oauth/authorize?client_id=${github_client_id}&state=${state}&scope=user:email`,
);
}
Expand All @@ -289,7 +299,7 @@ export async function onLinuxDOOAuthClicked(
) {
const state = await prepareOAuthState(options);
if (!state) return;
window.open(
redirectToOAuthUrl(
`https://connect.linux.do/oauth2/authorize?response_type=code&client_id=${linuxdo_client_id}&state=${state}`,
);
}
Expand All @@ -307,29 +317,39 @@ export async function onLinuxDOOAuthClicked(
export async function onCustomOAuthClicked(provider, options = {}) {
const state = await prepareOAuthState(options);
if (!state) return;

try {
const redirect_uri = `${window.location.origin}/oauth/${provider.slug}`;

// Check if authorization_endpoint is a full URL or relative path
let authUrl;
if (provider.authorization_endpoint.startsWith('http://') ||
provider.authorization_endpoint.startsWith('https://')) {
if (
provider.authorization_endpoint.startsWith('http://') ||
provider.authorization_endpoint.startsWith('https://')
) {
authUrl = new URL(provider.authorization_endpoint);
} else {
// Relative path - this is a configuration error, show error message
console.error('Custom OAuth authorization_endpoint must be a full URL:', provider.authorization_endpoint);
showError('OAuth 配置错误:授权端点必须是完整的 URL(以 http:// 或 https:// 开头)');
console.error(
'Custom OAuth authorization_endpoint must be a full URL:',
provider.authorization_endpoint,
);
showError(
'OAuth 配置错误:授权端点必须是完整的 URL(以 http:// 或 https:// 开头)',
);
return;
}

authUrl.searchParams.set('client_id', provider.client_id);
authUrl.searchParams.set('redirect_uri', redirect_uri);
authUrl.searchParams.set('response_type', 'code');
authUrl.searchParams.set('scope', provider.scopes || 'openid profile email');
authUrl.searchParams.set(
'scope',
provider.scopes || 'openid profile email',
);
authUrl.searchParams.set('state', state);
window.open(authUrl.toString());

redirectToOAuthUrl(authUrl);

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

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Description: Verify loading state handling in OAuth calling components

# Check LoginForm loading state handling
echo "=== LoginForm custom OAuth loading state ==="
rg -n -A10 -B2 'handleCustomOAuthClick' web/src/components/auth/LoginForm.jsx

echo -e "\n=== RegisterForm custom OAuth loading state ==="
rg -n -A10 -B2 'handleCustomOAuthClick' web/src/components/auth/RegisterForm.jsx

echo -e "\n=== AccountManagement custom OAuth handling ==="
rg -n -A5 -B2 'handleBindCustomOAuth|onCustomOAuthClicked' web/src/components/settings/personal/cards/AccountManagement.jsx

Repository: QuantumNous/new-api

Length of output: 3248


🏁 Script executed:

# Find the redirectToOAuthUrl function implementation
rg -n 'function redirectToOAuthUrl|const redirectToOAuthUrl|redirectToOAuthUrl\s*=' web/src/helpers/ -A15

Repository: QuantumNous/new-api

Length of output: 888


🏁 Script executed:

# Also check the api.js file around line 352 to understand context
cat -n web/src/helpers/api.js | sed -n '340,360p'

Repository: QuantumNous/new-api

Length of output: 811


🏁 Script executed:

# Check if there are any other references to this function to understand its usage
rg -n 'redirectToOAuthUrl' web/src/ -B2 -A2

Repository: QuantumNous/new-api

Length of output: 2025


Pass openInNewTab parameter for API consistency and preserve original new-window behavior.

The call at line 352 should pass { openInNewTab } to match the OIDC pattern (line 285). The original implementation used window.open(), which opens in a new window. The current redirectToOAuthUrl(authUrl) call defaults to same-window redirect via window.location.assign(), which changes the user experience:

  • Loading state visibility: The finally blocks in LoginForm.jsx (lines 399-403) and RegisterForm.jsx (lines 340-344) won't execute during same-window redirects. The setTimeout in RegisterForm (3000ms) will be abandoned when the page unloads, causing the loading spinner to disappear before OAuth completes. LoginForm already documents this limitation (line 400).

  • API consistency: Passing { openInNewTab } aligns with how OIDC calls this function (line 285) and maintains a consistent API contract.

  • Missing feedback in AccountManagement: The handleBindCustomOAuth function (lines 146-148) calls onCustomOAuthClicked without any loading state, leaving users without visual feedback when binding OAuth from account settings.

Recommendation: Pass { openInNewTab: true } to restore the original new-window behavior, or if switching to same-window, update the calling components to handle loading state appropriately.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@web/src/helpers/api.js` at line 352, Call redirectToOAuthUrl with the
open-in-new-tab option to restore original behavior and match the OIDC call
pattern: replace the bare redirectToOAuthUrl(authUrl) invocation with
redirectToOAuthUrl(authUrl, { openInNewTab }) (or redirectToOAuthUrl(authUrl, {
openInNewTab: true }) if you want to always open a new window). Update callers
that rely on loading states (LoginForm.jsx, RegisterForm.jsx and
AccountManagement's handleBindCustomOAuth/onCustomOAuthClicked) to pass the
appropriate openInNewTab value so the API contract remains consistent and UI
feedback remains correct.

} catch (error) {
console.error('Failed to initiate custom OAuth:', error);
showError('OAuth 登录失败:' + (error.message || '未知错误'));
Expand Down