fix(account): 重新授权不再清空 Extra 配置

Claude / OpenAI 账号重新授权走通用 PUT /accounts/:id 时,后端
UpdateAccount 会全量覆盖 account.Extra(仅保留 5 个 quota 用量键),
导致 base_rpm / window_cost_limit / window_cost_sticky_reserve /
max_sessions / quota_* / privacy_mode 等持久化配置全部丢失。

新增专用接口 POST /accounts/:id/apply-oauth-credentials,沿用
现有 /refresh 路径模式:Credentials-only update + Extra JSONB
key 级合并(UpdateAccountExtra) + ClearError + InvalidateToken。

作用域:Claude OAuth / Claude Cookie auth / OpenAI OAuth 三个
调用点。Gemini / Antigravity 现有路径本就不传 extra,保持不变。

顺带修复:旧重新授权路径未调用 InvalidateToken,导致重新授权后
首请求可能仍用缓存中的旧 token 而立即 401。

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
DaydreamCoding
2026-05-26 19:46:08 +08:00
committed by QTom
co-authored by Claude Opus 4.7
parent 9ef144874a
commit 11fe7de926
6 changed files with 144 additions and 20 deletions
+25
View File
@@ -204,6 +204,30 @@ export async function refreshCredentials(id: number): Promise<Account> {
return data
}
/**
* Apply OAuth credentials after re-authorization.
*
* Unlike `update()`, this endpoint:
* - never overwrites the whole `extra` JSONB (merges incrementally instead),
* so persistent settings like `base_rpm`, `window_cost_limit`, `max_sessions`,
* `quota_*` and `privacy_mode` are preserved
* - clears the account error and invalidates the token cache server-side
*/
export async function applyOAuthCredentials(
id: number,
payload: {
type: 'oauth' | 'setup-token'
credentials: Record<string, unknown>
extra?: Record<string, unknown>
}
): Promise<Account> {
const { data } = await apiClient.post<Account>(
`/admin/accounts/${id}/apply-oauth-credentials`,
payload
)
return data
}
/**
* Get account usage statistics
* @param id - Account ID
@@ -665,6 +689,7 @@ export const accountsAPI = {
toggleStatus,
testAccount,
refreshCredentials,
applyOAuthCredentials,
getStats,
clearError,
getUsage,
@@ -371,16 +371,12 @@ const handleExchangeCode = async () => {
const extra = oauthClient.buildExtraInfo(tokenInfo)
try {
// Update account with new credentials
await adminAPI.accounts.update(props.account.id, {
type: 'oauth', // OpenAI OAuth is always 'oauth' type
const updatedAccount = await adminAPI.accounts.applyOAuthCredentials(props.account.id, {
type: 'oauth',
credentials,
extra
})
// Clear error status after successful re-authorization
const updatedAccount = await adminAPI.accounts.clearError(props.account.id)
appStore.showSuccess(t('admin.accounts.reAuthorizedSuccess'))
emit('reauthorized', updatedAccount)
handleClose()
@@ -476,16 +472,12 @@ const handleExchangeCode = async () => {
const extra = claudeOAuth.buildExtraInfo(tokenInfo)
// Update account with new credentials and type
await adminAPI.accounts.update(props.account.id, {
type: addMethod.value, // Update type based on selected method
credentials: tokenInfo,
const updatedAccount = await adminAPI.accounts.applyOAuthCredentials(props.account.id, {
type: addMethod.value as 'oauth' | 'setup-token',
credentials: tokenInfo as unknown as Record<string, unknown>,
extra
})
// Clear error status after successful re-authorization
const updatedAccount = await adminAPI.accounts.clearError(props.account.id)
appStore.showSuccess(t('admin.accounts.reAuthorizedSuccess'))
emit('reauthorized', updatedAccount)
handleClose()
@@ -519,16 +511,12 @@ const handleCookieAuth = async (sessionKey: string) => {
const extra = claudeOAuth.buildExtraInfo(tokenInfo)
// Update account with new credentials and type
await adminAPI.accounts.update(props.account.id, {
type: addMethod.value, // Update type based on selected method
credentials: tokenInfo,
const updatedAccount = await adminAPI.accounts.applyOAuthCredentials(props.account.id, {
type: addMethod.value as 'oauth' | 'setup-token',
credentials: tokenInfo as unknown as Record<string, unknown>,
extra
})
// Clear error status after successful re-authorization
const updatedAccount = await adminAPI.accounts.clearError(props.account.id)
appStore.showSuccess(t('admin.accounts.reAuthorizedSuccess'))
emit('reauthorized', updatedAccount)
handleClose()