Spaces:
Running
Running
| import { getRequestBody } from '../utils/common.js'; | |
| import { | |
| handleGeminiCliOAuth, | |
| handleGeminiAntigravityOAuth, | |
| handleQwenOAuth, | |
| handleKiroOAuth, | |
| handleIFlowOAuth, | |
| handleCodexOAuth, | |
| batchImportKiroRefreshTokensStream, | |
| importAwsCredentials | |
| } from '../auth/oauth-handlers.js'; | |
| /** | |
| * 生成 OAuth 授权 URL | |
| */ | |
| export async function handleGenerateAuthUrl(req, res, currentConfig, providerType) { | |
| try { | |
| let authUrl = ''; | |
| let authInfo = {}; | |
| // 解析 options | |
| let options = {}; | |
| try { | |
| options = await getRequestBody(req); | |
| } catch (e) { | |
| // 如果没有请求体,使用默认空对象 | |
| } | |
| // 根据提供商类型生成授权链接并启动回调服务器 | |
| if (providerType === 'gemini-cli-oauth') { | |
| const result = await handleGeminiCliOAuth(currentConfig, options); | |
| authUrl = result.authUrl; | |
| authInfo = result.authInfo; | |
| } else if (providerType === 'gemini-antigravity') { | |
| const result = await handleGeminiAntigravityOAuth(currentConfig, options); | |
| authUrl = result.authUrl; | |
| authInfo = result.authInfo; | |
| } else if (providerType === 'openai-qwen-oauth') { | |
| const result = await handleQwenOAuth(currentConfig, options); | |
| authUrl = result.authUrl; | |
| authInfo = result.authInfo; | |
| } else if (providerType === 'claude-kiro-oauth') { | |
| // Kiro OAuth 支持多种认证方式 | |
| // options.method 可以是: 'google' | 'github' | 'builder-id' | |
| const result = await handleKiroOAuth(currentConfig, options); | |
| authUrl = result.authUrl; | |
| authInfo = result.authInfo; | |
| } else if (providerType === 'openai-iflow') { | |
| // iFlow OAuth 授权 | |
| const result = await handleIFlowOAuth(currentConfig, options); | |
| authUrl = result.authUrl; | |
| authInfo = result.authInfo; | |
| } else if (providerType === 'openai-codex-oauth') { | |
| // Codex OAuth(OAuth2 + PKCE) | |
| const result = await handleCodexOAuth(currentConfig, options); | |
| authUrl = result.authUrl; | |
| authInfo = result.authInfo; | |
| } else { | |
| res.writeHead(400, { 'Content-Type': 'application/json' }); | |
| res.end(JSON.stringify({ | |
| error: { | |
| message: `Unsupported provider type: ${providerType}` | |
| } | |
| })); | |
| return true; | |
| } | |
| res.writeHead(200, { 'Content-Type': 'application/json' }); | |
| res.end(JSON.stringify({ | |
| success: true, | |
| authUrl: authUrl, | |
| authInfo: authInfo | |
| })); | |
| return true; | |
| } catch (error) { | |
| console.error(`[UI API] Failed to generate auth URL for ${providerType}:`, error); | |
| res.writeHead(500, { 'Content-Type': 'application/json' }); | |
| res.end(JSON.stringify({ | |
| error: { | |
| message: `Failed to generate auth URL: ${error.message}` | |
| } | |
| })); | |
| return true; | |
| } | |
| } | |
| /** | |
| * 处理手动 OAuth 回调 | |
| */ | |
| export async function handleManualOAuthCallback(req, res) { | |
| try { | |
| const body = await getRequestBody(req); | |
| const { provider, callbackUrl, authMethod } = body; | |
| if (!provider || !callbackUrl) { | |
| res.writeHead(400, { 'Content-Type': 'application/json' }); | |
| res.end(JSON.stringify({ | |
| success: false, | |
| error: 'provider and callbackUrl are required' | |
| })); | |
| return true; | |
| } | |
| console.log(`[OAuth Manual Callback] Processing manual callback for ${provider}`); | |
| console.log(`[OAuth Manual Callback] Callback URL: ${callbackUrl}`); | |
| // 解析回调URL | |
| const url = new URL(callbackUrl); | |
| const code = url.searchParams.get('code'); | |
| const state = url.searchParams.get('state'); | |
| const token = url.searchParams.get('token'); | |
| if (!code && !token) { | |
| res.writeHead(400, { 'Content-Type': 'application/json' }); | |
| res.end(JSON.stringify({ | |
| success: false, | |
| error: 'Callback URL must contain code or token parameter' | |
| })); | |
| return true; | |
| } | |
| // 特殊处理 Codex OAuth 回调 | |
| if (provider === 'openai-codex-oauth' && code && state) { | |
| const { handleCodexOAuthCallback } = await import('../auth/oauth-handlers.js'); | |
| const result = await handleCodexOAuthCallback(code, state); | |
| res.writeHead(result.success ? 200 : 500, { 'Content-Type': 'application/json' }); | |
| res.end(JSON.stringify(result)); | |
| return true; | |
| } | |
| // 通过fetch请求本地OAuth回调服务器处理 | |
| // 使用localhost而不是原始hostname,确保请求到达本地服务器 | |
| const localUrl = new URL(callbackUrl); | |
| localUrl.hostname = 'localhost'; | |
| localUrl.protocol = 'http:'; | |
| try { | |
| const response = await fetch(localUrl.href); | |
| if (response.ok) { | |
| console.log(`[OAuth Manual Callback] Successfully processed callback for ${provider}`); | |
| res.writeHead(200, { 'Content-Type': 'application/json' }); | |
| res.end(JSON.stringify({ | |
| success: true, | |
| message: 'OAuth callback processed successfully' | |
| })); | |
| } else { | |
| const errorText = await response.text(); | |
| console.error(`[OAuth Manual Callback] Callback processing failed:`, errorText); | |
| res.writeHead(500, { 'Content-Type': 'application/json' }); | |
| res.end(JSON.stringify({ | |
| success: false, | |
| error: `Callback processing failed: ${response.status}` | |
| })); | |
| } | |
| } catch (fetchError) { | |
| console.error(`[OAuth Manual Callback] Failed to process callback:`, fetchError); | |
| res.writeHead(500, { 'Content-Type': 'application/json' }); | |
| res.end(JSON.stringify({ | |
| success: false, | |
| error: `Failed to process callback: ${fetchError.message}` | |
| })); | |
| } | |
| return true; | |
| } catch (error) { | |
| console.error('[OAuth Manual Callback] Error:', error); | |
| res.writeHead(500, { 'Content-Type': 'application/json' }); | |
| res.end(JSON.stringify({ | |
| success: false, | |
| error: error.message | |
| })); | |
| return true; | |
| } | |
| } | |
| /** | |
| * 批量导入 Kiro refreshToken(带实时进度 SSE) | |
| */ | |
| export async function handleBatchImportKiroTokens(req, res) { | |
| try { | |
| const body = await getRequestBody(req); | |
| const { refreshTokens, region } = body; | |
| if (!refreshTokens || !Array.isArray(refreshTokens) || refreshTokens.length === 0) { | |
| res.writeHead(400, { 'Content-Type': 'application/json' }); | |
| res.end(JSON.stringify({ | |
| success: false, | |
| error: 'refreshTokens array is required and must not be empty' | |
| })); | |
| return true; | |
| } | |
| console.log(`[Kiro Batch Import] Starting batch import of ${refreshTokens.length} tokens with SSE...`); | |
| // 设置 SSE 响应头 | |
| res.writeHead(200, { | |
| 'Content-Type': 'text/event-stream', | |
| 'Cache-Control': 'no-cache', | |
| 'Connection': 'keep-alive', | |
| 'X-Accel-Buffering': 'no' | |
| }); | |
| // 发送 SSE 事件的辅助函数 | |
| const sendSSE = (event, data) => { | |
| res.write(`event: ${event}\n`); | |
| res.write(`data: ${JSON.stringify(data)}\n\n`); | |
| }; | |
| // 发送开始事件 | |
| sendSSE('start', { total: refreshTokens.length }); | |
| // 执行流式批量导入 | |
| const result = await batchImportKiroRefreshTokensStream( | |
| refreshTokens, | |
| region || 'us-east-1', | |
| (progress) => { | |
| // 每处理完一个 token 发送进度更新 | |
| sendSSE('progress', progress); | |
| } | |
| ); | |
| console.log(`[Kiro Batch Import] Completed: ${result.success} success, ${result.failed} failed`); | |
| // 发送完成事件 | |
| sendSSE('complete', { | |
| success: true, | |
| total: result.total, | |
| successCount: result.success, | |
| failedCount: result.failed, | |
| details: result.details | |
| }); | |
| res.end(); | |
| return true; | |
| } catch (error) { | |
| console.error('[Kiro Batch Import] Error:', error); | |
| // 如果已经开始发送 SSE,则发送错误事件 | |
| if (res.headersSent) { | |
| res.write(`event: error\n`); | |
| res.write(`data: ${JSON.stringify({ error: error.message })}\n\n`); | |
| res.end(); | |
| } else { | |
| res.writeHead(500, { 'Content-Type': 'application/json' }); | |
| res.end(JSON.stringify({ | |
| success: false, | |
| error: error.message | |
| })); | |
| } | |
| return true; | |
| } | |
| } | |
| /** | |
| * 导入 AWS SSO 凭据用于 Kiro | |
| */ | |
| export async function handleImportAwsCredentials(req, res) { | |
| try { | |
| const body = await getRequestBody(req); | |
| const { credentials } = body; | |
| if (!credentials || typeof credentials !== 'object') { | |
| res.writeHead(400, { 'Content-Type': 'application/json' }); | |
| res.end(JSON.stringify({ | |
| success: false, | |
| error: 'credentials object is required' | |
| })); | |
| return true; | |
| } | |
| // 验证必需字段 - 需要四个字段都存在 | |
| const missingFields = []; | |
| if (!credentials.clientId) missingFields.push('clientId'); | |
| if (!credentials.clientSecret) missingFields.push('clientSecret'); | |
| if (!credentials.accessToken) missingFields.push('accessToken'); | |
| if (!credentials.refreshToken) missingFields.push('refreshToken'); | |
| if (missingFields.length > 0) { | |
| res.writeHead(400, { 'Content-Type': 'application/json' }); | |
| res.end(JSON.stringify({ | |
| success: false, | |
| error: `Missing required fields: ${missingFields.join(', ')}` | |
| })); | |
| return true; | |
| } | |
| console.log('[Kiro AWS Import] Starting AWS credentials import...'); | |
| const result = await importAwsCredentials(credentials); | |
| if (result.success) { | |
| console.log(`[Kiro AWS Import] Successfully imported credentials to: ${result.path}`); | |
| res.writeHead(200, { 'Content-Type': 'application/json' }); | |
| res.end(JSON.stringify({ | |
| success: true, | |
| path: result.path, | |
| message: 'AWS credentials imported successfully' | |
| })); | |
| } else { | |
| // 重复凭据返回 409 Conflict,其他错误返回 500 | |
| const statusCode = result.error === 'duplicate' ? 409 : 500; | |
| res.writeHead(statusCode, { 'Content-Type': 'application/json' }); | |
| res.end(JSON.stringify({ | |
| success: false, | |
| error: result.error, | |
| existingPath: result.existingPath || null | |
| })); | |
| } | |
| return true; | |
| } catch (error) { | |
| console.error('[Kiro AWS Import] Error:', error); | |
| res.writeHead(500, { 'Content-Type': 'application/json' }); | |
| res.end(JSON.stringify({ | |
| success: false, | |
| error: error.message | |
| })); | |
| return true; | |
| } | |
| } | |