feat(frontend): 新增 Gemini OAuth 授权流程
- 新增 /admin/gemini API 接口封装(generateAuthUrl, exchangeCode) - 新增 useGeminiOAuth composable 处理 Gemini OAuth 流程 - 新增 OAuthCallbackView 视图用于接收 OAuth 回调 - 支持 code/state 参数提取和 credentials 构建
This commit is contained in:
133
frontend/src/composables/useGeminiOAuth.ts
Normal file
133
frontend/src/composables/useGeminiOAuth.ts
Normal file
@@ -0,0 +1,133 @@
|
||||
import { ref } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { useAppStore } from '@/stores/app'
|
||||
import { adminAPI } from '@/api/admin'
|
||||
|
||||
export interface GeminiTokenInfo {
|
||||
access_token?: string
|
||||
refresh_token?: string
|
||||
token_type?: string
|
||||
scope?: string
|
||||
expires_at?: number | string
|
||||
project_id?: string
|
||||
[key: string]: unknown
|
||||
}
|
||||
|
||||
export function useGeminiOAuth() {
|
||||
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,
|
||||
redirectUri: string
|
||||
): Promise<boolean> => {
|
||||
loading.value = true
|
||||
authUrl.value = ''
|
||||
sessionId.value = ''
|
||||
state.value = ''
|
||||
error.value = ''
|
||||
|
||||
try {
|
||||
if (!redirectUri?.trim()) {
|
||||
error.value = t('admin.accounts.oauth.gemini.missingRedirectUri')
|
||||
appStore.showError(error.value)
|
||||
return false
|
||||
}
|
||||
|
||||
const payload: Record<string, unknown> = { redirect_uri: redirectUri.trim() }
|
||||
if (proxyId) payload.proxy_id = proxyId
|
||||
|
||||
const response = await adminAPI.gemini.generateAuthUrl(payload as any)
|
||||
authUrl.value = response.auth_url
|
||||
sessionId.value = response.session_id
|
||||
state.value = response.state
|
||||
return true
|
||||
} catch (err: any) {
|
||||
error.value = err.response?.data?.detail || t('admin.accounts.oauth.gemini.failedToGenerateUrl')
|
||||
appStore.showError(error.value)
|
||||
return false
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const exchangeAuthCode = async (params: {
|
||||
code: string
|
||||
sessionId: string
|
||||
state: string
|
||||
redirectUri: string
|
||||
proxyId?: number | null
|
||||
}): Promise<GeminiTokenInfo | null> => {
|
||||
const code = params.code?.trim()
|
||||
if (!code || !params.sessionId || !params.state || !params.redirectUri?.trim()) {
|
||||
error.value = t('admin.accounts.oauth.gemini.missingExchangeParams')
|
||||
return null
|
||||
}
|
||||
|
||||
loading.value = true
|
||||
error.value = ''
|
||||
|
||||
try {
|
||||
const payload: Record<string, unknown> = {
|
||||
session_id: params.sessionId,
|
||||
state: params.state,
|
||||
code,
|
||||
redirect_uri: params.redirectUri.trim()
|
||||
}
|
||||
if (params.proxyId) payload.proxy_id = params.proxyId
|
||||
|
||||
const tokenInfo = await adminAPI.gemini.exchangeCode(payload as any)
|
||||
return tokenInfo as GeminiTokenInfo
|
||||
} catch (err: any) {
|
||||
error.value = err.response?.data?.detail || t('admin.accounts.oauth.gemini.failedToExchangeCode')
|
||||
appStore.showError(error.value)
|
||||
return null
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const buildCredentials = (tokenInfo: GeminiTokenInfo): Record<string, unknown> => {
|
||||
let expiresAt: string | undefined
|
||||
if (typeof tokenInfo.expires_at === 'number' && Number.isFinite(tokenInfo.expires_at)) {
|
||||
expiresAt = Math.floor(tokenInfo.expires_at).toString()
|
||||
} else if (typeof tokenInfo.expires_at === 'string' && tokenInfo.expires_at.trim()) {
|
||||
expiresAt = tokenInfo.expires_at.trim()
|
||||
}
|
||||
|
||||
return {
|
||||
access_token: tokenInfo.access_token,
|
||||
refresh_token: tokenInfo.refresh_token,
|
||||
token_type: tokenInfo.token_type,
|
||||
expires_at: expiresAt,
|
||||
scope: tokenInfo.scope,
|
||||
project_id: tokenInfo.project_id
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
authUrl,
|
||||
sessionId,
|
||||
state,
|
||||
loading,
|
||||
error,
|
||||
resetState,
|
||||
generateAuthUrl,
|
||||
exchangeAuthCode,
|
||||
buildCredentials
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user