(), {
showAgentIdentityOption: false,
showCodexPatOption: false,
showSsoOption: false,
+ showEmailPasswordOption: false,
showManualOption: true,
initialInputMethod: 'manual',
platform: 'anthropic',
@@ -877,6 +964,7 @@ const emit = defineEmits<{
'import-codex-session': [content: string]
'import-codex-pat': [accessToken: string]
'import-sso': [content: string]
+ 'authorize-password': [emailPasswordInput: string]
'update:inputMethod': [method: AuthInputMethod]
}>()
@@ -922,6 +1010,7 @@ const sessionTokenInput = ref('')
const codexSessionInput = ref('')
const codexPATInput = ref('')
const ssoCookieInput = ref('')
+const emailPasswordInput = ref('')
const showHelpDialog = ref(false)
const oauthState = ref('')
const projectId = ref('')
@@ -937,7 +1026,8 @@ const methodOptionCount = computed(() => [
props.showCodexSessionImportOption,
props.showAgentIdentityOption,
props.showCodexPatOption,
- props.showSsoOption
+ props.showSsoOption,
+ props.showEmailPasswordOption
].filter(Boolean).length)
const showMethodSelection = computed(() => methodOptionCount.value > 1)
@@ -977,6 +1067,19 @@ const parsedSSOCount = computed(() => {
.filter((item) => item).length
})
+const parsedEmailPasswordCount = computed(() => {
+ return emailPasswordInput.value
+ .split('\n')
+ .map((item) => item.trim())
+ .filter((item) => item && item.includes('----')).length
+})
+
+const handleAuthorizePassword = () => {
+ if (emailPasswordInput.value.trim()) {
+ emit('authorize-password', emailPasswordInput.value)
+ }
+}
+
// Watchers
watch(() => props.initialInputMethod, (newVal) => {
inputMethod.value = newVal
@@ -1081,6 +1184,7 @@ defineExpose({
codexSession: codexSessionInput,
codexPAT: codexPATInput,
ssoCookie: ssoCookieInput,
+ emailPassword: emailPasswordInput,
inputMethod,
reset: () => {
authCodeInput.value = ''
@@ -1092,6 +1196,7 @@ defineExpose({
codexSessionInput.value = ''
codexPATInput.value = ''
ssoCookieInput.value = ''
+ emailPasswordInput.value = ''
inputMethod.value = props.initialInputMethod
showHelpDialog.value = false
}
diff --git a/frontend/src/components/account/__tests__/CreateAccountModal.grok.spec.ts b/frontend/src/components/account/__tests__/CreateAccountModal.grok.spec.ts
index 1d0111e42c..7dbe40520b 100644
--- a/frontend/src/components/account/__tests__/CreateAccountModal.grok.spec.ts
+++ b/frontend/src/components/account/__tests__/CreateAccountModal.grok.spec.ts
@@ -23,9 +23,19 @@ describe('CreateAccountModal Grok account types', () => {
expect(source).toContain('form.platform === \'grok\' && isOAuthFlow')
})
- it('validates and applies upstream config on all three Grok OAuth create paths', () => {
- // 授权码兑换 / RT 批量 / SSO 批量 3 处调用(定义为箭头函数,不计入)
- expect(source.match(/validateGrokOAuthUpstreamConfig\(\)/g)?.length).toBe(3)
- expect(source.match(/applyGrokOAuthUpstreamConfig\(credentials\)/g)?.length).toBe(3)
+ it('validates and applies upstream config on Grok OAuth create paths', () => {
+ // 授权码兑换 / RT 批量 / SSO 批量 / 密码登录(4 条路径)
+ expect(source.match(/validateGrokOAuthUpstreamConfig\(\)/g)?.length).toBeGreaterThanOrEqual(4)
+ expect(source.match(/applyGrokOAuthUpstreamConfig\(credentials\)/g)?.length).toBeGreaterThanOrEqual(4)
+ })
+
+ it('wires Grok password authorize path without storing password/SSO fields', () => {
+ expect(source).toContain('show-email-password-option')
+ expect(source).toContain('@authorize-password="handleGrokAuthorizePassword"')
+ expect(source).toContain('handleGrokAuthorizePassword')
+ expect(source).toContain('grokOAuth.authorizePassword')
+ expect(source).toContain('grokOAuth.buildCredentials')
+ // Password only for authorize call; credentials come from buildCredentials
+ expect(source).toContain('email----password')
})
})
diff --git a/frontend/src/composables/useAccountOAuth.ts b/frontend/src/composables/useAccountOAuth.ts
index e78a19a411..49cb5c48ed 100644
--- a/frontend/src/composables/useAccountOAuth.ts
+++ b/frontend/src/composables/useAccountOAuth.ts
@@ -3,7 +3,18 @@ import { useAppStore } from '@/stores/app'
import { adminAPI } from '@/api/admin'
export type AddMethod = 'oauth' | 'setup-token'
-export type AuthInputMethod = 'manual' | 'cookie' | 'refresh_token' | 'mobile_refresh_token' | 'session_token' | 'access_token' | 'codex_session' | 'agent_identity' | 'codex_pat' | 'sso_cookie'
+export type AuthInputMethod =
+ | 'manual'
+ | 'cookie'
+ | 'refresh_token'
+ | 'mobile_refresh_token'
+ | 'session_token'
+ | 'access_token'
+ | 'codex_session'
+ | 'agent_identity'
+ | 'codex_pat'
+ | 'sso_cookie'
+ | 'email_password'
export interface OAuthState {
authUrl: string
diff --git a/frontend/src/i18n/locales/en/admin/accounts.ts b/frontend/src/i18n/locales/en/admin/accounts.ts
index 283a33d386..ff05fd4761 100644
--- a/frontend/src/i18n/locales/en/admin/accounts.ts
+++ b/frontend/src/i18n/locales/en/admin/accounts.ts
@@ -1009,6 +1009,17 @@ export default {
ssoCookieLabel: 'Grok Web SSO Key',
ssoCookiePlaceholder: 'One SSO key per line\nSupports multiple, one per line',
ssoCookieHint: 'One SSO key per line. Multiple keys are imported with 3-way concurrency; expect about 90 seconds per batch. Use a matching-region proxy if needed.',
+ emailPasswordAuth: 'Email + password',
+ emailPasswordDesc:
+ 'Sign in with a Grok web email and password. The server uses the password only to obtain an ephemeral SSO cookie, then converts it to Build OAuth credentials. Neither the password nor raw SSO is stored on the account.',
+ emailPasswordInputLabel: 'email----password',
+ emailPasswordPlaceholder: 'user@example.com----your-password\nMultiple lines supported',
+ emailPasswordHint:
+ 'Format: email----password (password may contain -). Requires YesCaptcha keys; use a matching-region proxy when needed.',
+ pleaseEnterPassword: 'Please enter email----password (one per line)',
+ pleaseEnterSSOToken: 'Please enter an SSO token',
+ failedToValidateSSO: 'Failed to validate Grok SSO',
+ failedToAuthorizePassword: 'Grok password authorization failed',
convertingSSO: 'Converting...',
convertSSOAndCreate: 'Convert & Create Account',
validating: 'Validating...',
diff --git a/frontend/src/i18n/locales/zh/admin/accounts.ts b/frontend/src/i18n/locales/zh/admin/accounts.ts
index 63608cb8d4..24b33dd5cc 100644
--- a/frontend/src/i18n/locales/zh/admin/accounts.ts
+++ b/frontend/src/i18n/locales/zh/admin/accounts.ts
@@ -1062,6 +1062,16 @@ export default {
ssoCookieLabel: 'Grok Web SSO Key',
ssoCookiePlaceholder: '每行一个 SSO key\n支持多个,每行一个',
ssoCookieHint: '每行一个 SSO key;多个 key 会 3 路并发导入,耗时约 90 秒 × 批次数,建议使用对应地区代理。',
+ emailPasswordAuth: '邮箱密码登录',
+ emailPasswordDesc:
+ '使用 Grok 网页邮箱与密码登录。服务端仅用密码换取临时 SSO 再转 Build OAuth;密码与 raw SSO 均不会写入账号凭据。',
+ emailPasswordInputLabel: '邮箱----密码',
+ emailPasswordPlaceholder: 'user@example.com----your-password\n支持多个,每行一组',
+ emailPasswordHint: '格式:email----password(密码可含 -)。需要配置 YesCaptcha 密钥;建议搭配代理。',
+ pleaseEnterPassword: '请输入 email----password(每行一组)',
+ pleaseEnterSSOToken: '请输入 SSO Token',
+ failedToValidateSSO: '校验 Grok SSO 失败',
+ failedToAuthorizePassword: 'Grok 密码授权失败',
convertingSSO: '转换中...',
convertSSOAndCreate: '转换并创建账号',
validating: '验证中...',