Files
sub2api-overdraft/frontend/src/composables/useGrokOAuth.ts
T
jinfeijie bot ad4bf5c60d feat(grok): 支持 Web SSO 批量导入并转换为 Build OAuth
新增 Grok Web SSO → xAI Device Flow → Grok Build OAuth 导入链路,
支持管理员批量粘贴 SSO key 创建 OAuth 账号。

- 后端:ConvertSSOToBuild、ConvertFromSSO、POST /admin/grok/sso-to-oauth
- 批量:3 worker 并发,失败跳过并汇总 created/failed,worker panic recover
- 无 refresh_token 时写入 expires_at 并强制 auto_pause_on_expired
- 前端:SSO Cookie 导入入口、动态超时、中英文案、部分成功不关弹窗
- 测试:pkg/service/handler/前端超时单测;本地 Docker 真实 SSO e2e 通过
2026-07-14 01:09:07 +08:00

157 lines
4.4 KiB
TypeScript

import { ref } from 'vue'
import { useI18n } from 'vue-i18n'
import { useAppStore } from '@/stores/app'
import { adminAPI } from '@/api/admin'
import type { GrokTokenInfo } from '@/api/admin/grok'
import { extractApiErrorMessage, extractI18nErrorMessage } from '@/utils/apiError'
export function useGrokOAuth() {
const appStore = useAppStore()
const { t } = useI18n()
const authUrl = ref('')
const sessionId = ref('')
const state = ref('')
const loading = ref(false)
const error = ref('')
const resetState = () => {
authUrl.value = ''
sessionId.value = ''
state.value = ''
loading.value = false
error.value = ''
}
const generateAuthUrl = async (proxyId: number | null | undefined): Promise<boolean> => {
loading.value = true
authUrl.value = ''
sessionId.value = ''
state.value = ''
error.value = ''
try {
const payload: Record<string, unknown> = {}
if (proxyId) payload.proxy_id = proxyId
const response = await adminAPI.grok.generateAuthUrl(payload)
authUrl.value = response.auth_url
sessionId.value = response.session_id
state.value = response.state
return true
} catch (err: any) {
error.value = extractApiErrorMessage(err, t('admin.accounts.oauth.grok.failedToGenerateUrl'))
appStore.showError(error.value)
return false
} finally {
loading.value = false
}
}
const exchangeAuthCode = async (params: {
code: string
sessionId: string
state: string
proxyId?: number | null
}): Promise<GrokTokenInfo | null> => {
const code = params.code?.trim()
if (!code || !params.sessionId || !params.state) {
error.value = t('admin.accounts.oauth.grok.missingExchangeParams')
return null
}
loading.value = true
error.value = ''
try {
const payload: Record<string, unknown> = {
session_id: params.sessionId,
state: params.state,
code
}
if (params.proxyId) payload.proxy_id = params.proxyId
return await adminAPI.grok.exchangeCode(payload as any)
} catch (err: any) {
error.value = extractI18nErrorMessage(
err,
t,
'admin.accounts.oauth.grok.errors',
t('admin.accounts.oauth.grok.failedToExchangeCode')
)
appStore.showError(error.value)
return null
} finally {
loading.value = false
}
}
const validateRefreshToken = async (
refreshToken: string,
proxyId?: number | null
): Promise<GrokTokenInfo | null> => {
if (!refreshToken.trim()) {
error.value = t('admin.accounts.oauth.grok.pleaseEnterRefreshToken')
return null
}
loading.value = true
error.value = ''
try {
return await adminAPI.grok.refreshGrokToken(refreshToken.trim(), proxyId)
} catch (err: any) {
error.value = extractI18nErrorMessage(
err,
t,
'admin.accounts.oauth.grok.errors',
t('admin.accounts.oauth.grok.failedToValidateRT')
)
return null
} finally {
loading.value = false
}
}
const buildCredentials = (tokenInfo: GrokTokenInfo): Record<string, unknown> => {
const credentials: Record<string, unknown> = {
access_token: tokenInfo.access_token,
token_type: tokenInfo.token_type,
expires_at: tokenInfo.expires_at,
client_id: tokenInfo.client_id,
scope: tokenInfo.scope,
email: tokenInfo.email,
sub: tokenInfo.sub,
team_id: tokenInfo.team_id,
subscription_tier: tokenInfo.subscription_tier,
entitlement_status: tokenInfo.entitlement_status,
base_url: 'https://cli-chat-proxy.grok.com/v1'
}
if (tokenInfo.refresh_token) credentials.refresh_token = tokenInfo.refresh_token
if (tokenInfo.id_token) credentials.id_token = tokenInfo.id_token
return Object.fromEntries(Object.entries(credentials).filter(([, value]) => value !== undefined && value !== ''))
}
const buildExtraInfo = (tokenInfo: GrokTokenInfo): Record<string, unknown> => {
const extra: Record<string, unknown> = {}
if (tokenInfo.email) extra.email = tokenInfo.email
if (tokenInfo.subscription_tier) extra.subscription_tier = tokenInfo.subscription_tier
if (tokenInfo.entitlement_status) extra.entitlement_status = tokenInfo.entitlement_status
return extra
}
return {
authUrl,
sessionId,
state,
loading,
error,
resetState,
generateAuthUrl,
exchangeAuthCode,
validateRefreshToken,
buildCredentials,
buildExtraInfo
}
}