Merge pull request #1829 from ZHOUKAILIAN/feature/codex-oauth-proxy-message

fix: 明确 OpenAI OAuth 未配置代理时的错误提示
This commit is contained in:
Wesley Liddick
2026-04-23 16:55:04 +08:00
committed by GitHub
6 changed files with 99 additions and 3 deletions

View File

@@ -6,6 +6,19 @@ vi.mock('@/stores/app', () => ({
})
}))
vi.mock('vue-i18n', () => ({
useI18n: () => ({
t: (key: string) => {
const messages: Record<string, string> = {
'admin.accounts.oauth.openai.failedToExchangeCode': 'OpenAI 授权码兑换失败',
'admin.accounts.oauth.openai.errors.OPENAI_OAUTH_PROXY_REQUIRED':
'未设置代理,当前服务器无法直连 OpenAI导致 OpenAI OAuth 请求失败。请先选择可访问 OpenAI 的代理后重试;如果授权码已失效,请重新生成授权链接。'
}
return messages[key] ?? key
}
})
}))
vi.mock('@/api/admin', () => ({
adminAPI: {
accounts: {
@@ -17,6 +30,7 @@ vi.mock('@/api/admin', () => ({
}))
import { useOpenAIOAuth } from '@/composables/useOpenAIOAuth'
import { adminAPI } from '@/api/admin'
describe('useOpenAIOAuth.buildCredentials', () => {
it('should keep client_id when token response contains it', () => {
@@ -46,3 +60,21 @@ describe('useOpenAIOAuth.buildCredentials', () => {
expect(creds.refresh_token).toBe('rt')
})
})
describe('useOpenAIOAuth.exchangeAuthCode', () => {
it('shows a clear proxy hint when code exchange fails without a proxy', async () => {
vi.mocked(adminAPI.accounts.exchangeCode).mockRejectedValueOnce({
status: 502,
reason: 'OPENAI_OAUTH_PROXY_REQUIRED',
message: 'OpenAI OAuth token exchange failed: no proxy is configured.'
})
const oauth = useOpenAIOAuth()
const tokenInfo = await oauth.exchangeAuthCode('code', 'session-id', 'state')
expect(tokenInfo).toBeNull()
expect(oauth.error.value).toBe(
'未设置代理,当前服务器无法直连 OpenAI导致 OpenAI OAuth 请求失败。请先选择可访问 OpenAI 的代理后重试;如果授权码已失效,请重新生成授权链接。'
)
})
})

View File

@@ -1,6 +1,8 @@
import { ref } from 'vue'
import { useI18n } from 'vue-i18n'
import { useAppStore } from '@/stores/app'
import { adminAPI } from '@/api/admin'
import { extractApiErrorMessage, extractI18nErrorMessage } from '@/utils/apiError'
export interface OpenAITokenInfo {
access_token?: string
@@ -26,6 +28,7 @@ export type OpenAIOAuthPlatform = 'openai'
export function useOpenAIOAuth() {
const appStore = useAppStore()
const { t } = useI18n()
const endpointPrefix = '/admin/openai'
// State
@@ -78,7 +81,7 @@ export function useOpenAIOAuth() {
}
return true
} catch (err: any) {
error.value = err.response?.data?.detail || 'Failed to generate OpenAI auth URL'
error.value = extractApiErrorMessage(err, t('admin.accounts.oauth.openai.failedToGenerateUrl'))
appStore.showError(error.value)
return false
} finally {
@@ -114,7 +117,12 @@ export function useOpenAIOAuth() {
const tokenInfo = await adminAPI.accounts.exchangeCode(`${endpointPrefix}/exchange-code`, payload)
return tokenInfo as OpenAITokenInfo
} catch (err: any) {
error.value = err.response?.data?.detail || 'Failed to exchange OpenAI auth code'
error.value = extractI18nErrorMessage(
err,
t,
'admin.accounts.oauth.openai.errors',
t('admin.accounts.oauth.openai.failedToExchangeCode')
)
appStore.showError(error.value)
return null
} finally {
@@ -147,7 +155,12 @@ export function useOpenAIOAuth() {
)
return tokenInfo as OpenAITokenInfo
} catch (err: any) {
error.value = err.response?.data?.detail || err.message || 'Failed to validate refresh token'
error.value = extractI18nErrorMessage(
err,
t,
'admin.accounts.oauth.openai.errors',
t('admin.accounts.oauth.openai.failedToValidateRT')
)
appStore.showError(error.value)
return null
} finally {

View File

@@ -2794,6 +2794,13 @@ export default {
'Option 1: Copy the complete URL\n(http://localhost:xxx/auth/callback?code=...)\nOption 2: Copy only the code parameter value',
authCodeHint:
'You can copy the entire URL or just the code parameter value, the system will auto-detect',
failedToGenerateUrl: 'Failed to generate OpenAI auth URL',
failedToExchangeCode: 'Failed to exchange OpenAI auth code',
failedToValidateRT: 'Failed to validate refresh token',
errors: {
OPENAI_OAUTH_PROXY_REQUIRED:
'No proxy is configured and this server could not reach OpenAI directly, so the OpenAI OAuth request failed. Select a proxy that can access OpenAI and retry; if the authorization code has expired, regenerate the authorization URL.'
},
// Refresh Token auth
refreshTokenAuth: 'Manual RT Input',
refreshTokenDesc: 'Enter your existing OpenAI Refresh Token(s). Supports batch input (one per line). The system will automatically validate and create accounts.',

View File

@@ -2929,6 +2929,13 @@ export default {
authCodePlaceholder:
'方式1复制完整的链接\n(http://localhost:xxx/auth/callback?code=...)\n方式2仅复制 code 参数的值',
authCodeHint: '您可以直接复制整个链接或仅复制 code 参数值,系统会自动识别',
failedToGenerateUrl: '生成 OpenAI 授权链接失败',
failedToExchangeCode: 'OpenAI 授权码兑换失败',
failedToValidateRT: '验证 Refresh Token 失败',
errors: {
OPENAI_OAUTH_PROXY_REQUIRED:
'未设置代理,当前服务器无法直连 OpenAI导致 OpenAI OAuth 请求失败。请先选择可访问 OpenAI 的代理后重试;如果授权码已失效,请重新生成授权链接。'
},
// Refresh Token auth
refreshTokenAuth: '手动输入 RT',
refreshTokenDesc: '输入您已有的 OpenAI Refresh Token支持批量输入每行一个系统将自动验证并创建账号。',