新增 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 通过
157 lines
4.4 KiB
TypeScript
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
|
|
}
|
|
}
|