Jaasomn
Initial deployment
ceb3821
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;
}
}