feat(sora): 对齐 Sora OAuth 流程并隔离网关请求路径

- 新增并接通 Sora 专用 OAuth 接口与 ST/RT 换取能力
- 完成前端 Sora 授权、RT/ST 手动导入与账号创建流程
- 强化 Sora token 恢复、转发日志与网关路由隔离行为
- 补充后端服务层与路由层相关测试覆盖

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
yangjianbo
2026-02-19 08:02:56 +08:00
parent 36bb327024
commit 900cce20a1
39 changed files with 2561 additions and 283 deletions

View File

@@ -3,7 +3,7 @@ import { useAppStore } from '@/stores/app'
import { adminAPI } from '@/api/admin'
export type AddMethod = 'oauth' | 'setup-token'
export type AuthInputMethod = 'manual' | 'cookie' | 'refresh_token'
export type AuthInputMethod = 'manual' | 'cookie' | 'refresh_token' | 'session_token'
export interface OAuthState {
authUrl: string

View File

@@ -19,12 +19,21 @@ export interface OpenAITokenInfo {
[key: string]: unknown
}
export function useOpenAIOAuth() {
export type OpenAIOAuthPlatform = 'openai' | 'sora'
interface UseOpenAIOAuthOptions {
platform?: OpenAIOAuthPlatform
}
export function useOpenAIOAuth(options?: UseOpenAIOAuthOptions) {
const appStore = useAppStore()
const oauthPlatform = options?.platform ?? 'openai'
const endpointPrefix = oauthPlatform === 'sora' ? '/admin/sora' : '/admin/openai'
// State
const authUrl = ref('')
const sessionId = ref('')
const oauthState = ref('')
const loading = ref(false)
const error = ref('')
@@ -32,6 +41,7 @@ export function useOpenAIOAuth() {
const resetState = () => {
authUrl.value = ''
sessionId.value = ''
oauthState.value = ''
loading.value = false
error.value = ''
}
@@ -44,6 +54,7 @@ export function useOpenAIOAuth() {
loading.value = true
authUrl.value = ''
sessionId.value = ''
oauthState.value = ''
error.value = ''
try {
@@ -56,11 +67,17 @@ export function useOpenAIOAuth() {
}
const response = await adminAPI.accounts.generateAuthUrl(
'/admin/openai/generate-auth-url',
`${endpointPrefix}/generate-auth-url`,
payload
)
authUrl.value = response.auth_url
sessionId.value = response.session_id
try {
const parsed = new URL(response.auth_url)
oauthState.value = parsed.searchParams.get('state') || ''
} catch {
oauthState.value = ''
}
return true
} catch (err: any) {
error.value = err.response?.data?.detail || 'Failed to generate OpenAI auth URL'
@@ -75,10 +92,11 @@ export function useOpenAIOAuth() {
const exchangeAuthCode = async (
code: string,
currentSessionId: string,
state: string,
proxyId?: number | null
): Promise<OpenAITokenInfo | null> => {
if (!code.trim() || !currentSessionId) {
error.value = 'Missing auth code or session ID'
if (!code.trim() || !currentSessionId || !state.trim()) {
error.value = 'Missing auth code, session ID, or state'
return null
}
@@ -86,15 +104,16 @@ export function useOpenAIOAuth() {
error.value = ''
try {
const payload: { session_id: string; code: string; proxy_id?: number } = {
const payload: { session_id: string; code: string; state: string; proxy_id?: number } = {
session_id: currentSessionId,
code: code.trim()
code: code.trim(),
state: state.trim()
}
if (proxyId) {
payload.proxy_id = proxyId
}
const tokenInfo = await adminAPI.accounts.exchangeCode('/admin/openai/exchange-code', payload)
const tokenInfo = await adminAPI.accounts.exchangeCode(`${endpointPrefix}/exchange-code`, payload)
return tokenInfo as OpenAITokenInfo
} catch (err: any) {
error.value = err.response?.data?.detail || 'Failed to exchange OpenAI auth code'
@@ -120,7 +139,11 @@ export function useOpenAIOAuth() {
try {
// Use dedicated refresh-token endpoint
const tokenInfo = await adminAPI.accounts.refreshOpenAIToken(refreshToken.trim(), proxyId)
const tokenInfo = await adminAPI.accounts.refreshOpenAIToken(
refreshToken.trim(),
proxyId,
`${endpointPrefix}/refresh-token`
)
return tokenInfo as OpenAITokenInfo
} catch (err: any) {
error.value = err.response?.data?.detail || 'Failed to validate refresh token'
@@ -131,6 +154,33 @@ export function useOpenAIOAuth() {
}
}
// Validate Sora session token and get access token
const validateSessionToken = async (
sessionToken: string,
proxyId?: number | null
): Promise<OpenAITokenInfo | null> => {
if (!sessionToken.trim()) {
error.value = 'Missing session token'
return null
}
loading.value = true
error.value = ''
try {
const tokenInfo = await adminAPI.accounts.validateSoraSessionToken(
sessionToken.trim(),
proxyId,
`${endpointPrefix}/st2at`
)
return tokenInfo as OpenAITokenInfo
} catch (err: any) {
error.value = err.response?.data?.detail || 'Failed to validate session token'
appStore.showError(error.value)
return null
} finally {
loading.value = false
}
}
// Build credentials for OpenAI OAuth account
const buildCredentials = (tokenInfo: OpenAITokenInfo): Record<string, unknown> => {
const creds: Record<string, unknown> = {
@@ -172,6 +222,7 @@ export function useOpenAIOAuth() {
// State
authUrl,
sessionId,
oauthState,
loading,
error,
// Methods
@@ -179,6 +230,7 @@ export function useOpenAIOAuth() {
generateAuthUrl,
exchangeAuthCode,
validateRefreshToken,
validateSessionToken,
buildCredentials,
buildExtraInfo
}