From 9b7ea3aabf2874d80f99f29e2ef094881a532868 Mon Sep 17 00:00:00 2001 From: IanShaw027 Date: Fri, 7 Aug 2026 13:18:59 +0800 Subject: [PATCH] =?UTF-8?q?feat(grok):=20=E5=88=9B=E5=BB=BA=E8=B4=A6?= =?UTF-8?q?=E5=8F=B7=E6=B5=81=E7=A8=8B=E6=8E=A5=E5=85=A5=E9=82=AE=E7=AE=B1?= =?UTF-8?q?=E5=AF=86=E7=A0=81=E7=99=BB=E5=BD=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 在 OAuth 授权流中增加 email----password 入口,CreateAccount 批量调用 authorizePassword 后经 buildCredentials 建号;密码与 raw SSO 不落库。 SSO Cookie 批量导入路径保持不变。 --- .../components/account/CreateAccountModal.vue | 111 ++++++++++++++++++ .../account/OAuthAuthorizationFlow.vue | 107 ++++++++++++++++- .../__tests__/CreateAccountModal.grok.spec.ts | 18 ++- frontend/src/composables/useAccountOAuth.ts | 13 +- .../src/i18n/locales/en/admin/accounts.ts | 11 ++ .../src/i18n/locales/zh/admin/accounts.ts | 10 ++ 6 files changed, 264 insertions(+), 6 deletions(-) diff --git a/frontend/src/components/account/CreateAccountModal.vue b/frontend/src/components/account/CreateAccountModal.vue index 22a499ee65..b450444877 100644 --- a/frontend/src/components/account/CreateAccountModal.vue +++ b/frontend/src/components/account/CreateAccountModal.vue @@ -3212,6 +3212,7 @@ :show-agent-identity-option="form.platform === 'openai'" :show-codex-pat-option="form.platform === 'openai'" :show-sso-option="form.platform === 'grok'" + :show-email-password-option="form.platform === 'grok'" :show-manual-option="true" :initial-input-method="'manual'" :platform="form.platform" @@ -3224,6 +3225,7 @@ @import-codex-session="handleOpenAIImportCodexSession" @import-codex-pat="handleOpenAIImportCodexPAT" @import-sso="handleGrokImportSSO" + @authorize-password="handleGrokAuthorizePassword" /> @@ -5476,6 +5478,115 @@ const handleGrokImportSSO = async (ssoInput: string) => { } } +/** + * Grok password login: each line is email----password. + * Password is only used for the authorize API call; buildCredentials never stores it. + */ +const handleGrokAuthorizePassword = async (emailPasswordInput: string) => { + if (!emailPasswordInput.trim()) return + if (!validateGrokOAuthUpstreamConfig()) return + + const lines = emailPasswordInput + .split('\n') + .map((line) => line.trim()) + .filter((line) => line && line.includes('----')) + + if (lines.length === 0) { + grokOAuth.error.value = t( + 'admin.accounts.oauth.grok.pleaseEnterPassword', + 'Please enter email----password (one per line)' + ) + return + } + + grokOAuth.loading.value = true + grokOAuth.error.value = '' + + let successCount = 0 + let failedCount = 0 + const errors: string[] = [] + + try { + for (let i = 0; i < lines.length; i++) { + try { + const tokenInfo = await grokOAuth.authorizePassword(lines[i], form.proxy_id) + if (!tokenInfo) { + failedCount++ + errors.push(`#${i + 1}: ${grokOAuth.error.value || 'Authorization failed'}`) + grokOAuth.error.value = '' + continue + } + + const credentials = grokOAuth.buildCredentials(tokenInfo) + applyGrokOAuthUpstreamConfig(credentials) + const extra = grokOAuth.buildExtraInfo(tokenInfo) + const accountName = + lines.length > 1 + ? `${form.name || tokenInfo.email || 'Grok OAuth Account'} #${i + 1}` + : form.name || tokenInfo.email || 'Grok OAuth Account' + + const modelMapping = buildModelMappingObject( + modelRestrictionMode.value, + allowedModels.value, + modelMappings.value + ) + if (modelMapping) { + credentials.model_mapping = modelMapping + } + if (!applyTempUnschedConfig(credentials)) { + return + } + + await adminAPI.accounts.create({ + name: accountName, + notes: form.notes, + platform: 'grok', + type: 'oauth', + credentials, + extra, + proxy_id: form.proxy_id, + concurrency: form.concurrency, + load_factor: form.load_factor ?? undefined, + priority: form.priority, + rate_multiplier: form.rate_multiplier, + group_ids: form.group_ids, + expires_at: form.expires_at, + auto_pause_on_expired: autoPauseOnExpired.value + }) + successCount++ + } catch (error: any) { + failedCount++ + const errMsg = error.response?.data?.detail || error.message || 'Unknown error' + errors.push(`#${i + 1}: ${errMsg}`) + } + } + + if (successCount > 0 && failedCount === 0) { + appStore.showSuccess( + lines.length > 1 + ? t('admin.accounts.oauth.batchSuccess', { count: successCount }) + : t('admin.accounts.accountCreated') + ) + emit('created') + handleClose() + } else if (successCount > 0) { + appStore.showWarning( + t('admin.accounts.oauth.batchPartialSuccess', { + success: successCount, + failed: failedCount + }) + ) + grokOAuth.error.value = errors.join('\n') + emit('created') + } else { + grokOAuth.error.value = errors.join('\n') + appStore.showError(t('admin.accounts.oauth.batchFailed')) + } + } finally { + grokOAuth.loading.value = false + } +} + // OpenAI OAuth 授权码兑换 const handleOpenAIExchange = async (authCode: string) => { const oauthClient = openaiOAuth diff --git a/frontend/src/components/account/OAuthAuthorizationFlow.vue b/frontend/src/components/account/OAuthAuthorizationFlow.vue index f45aeeb5d7..98ae7fe519 100644 --- a/frontend/src/components/account/OAuthAuthorizationFlow.vue +++ b/frontend/src/components/account/OAuthAuthorizationFlow.vue @@ -59,6 +59,17 @@ t(getOAuthKey('ssoCookieAuth')) }} +