import { OAuth2Client } from 'google-auth-library';
import http from 'http';
import fs from 'fs';
import path from 'path';
import os from 'os';
import crypto from 'crypto';
import open from 'open';
import { broadcastEvent } from './ui-manager.js';
import { autoLinkProviderConfigs } from './service-manager.js';
import { CONFIG } from './config-manager.js';
/**
* OAuth 提供商配置
*/
const OAUTH_PROVIDERS = {
'gemini-cli-oauth': {
clientId: '681255809395-oo8ft2oprdrnp9e3aqf6av3hmdib135j.apps.googleusercontent.com',
clientSecret: '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',
port: 8085,
credentialsDir: '.gemini',
credentialsFile: 'oauth_creds.json',
scope: ['https://www.googleapis.com/auth/cloud-platform'],
logPrefix: '[Gemini Auth]'
},
'gemini-antigravity': {
clientId: '1071006060591-tmhssin2h21lcre235vtolojh4g403ep.apps.googleusercontent.com',
clientSecret: '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',
port: 8086,
credentialsDir: '.antigravity',
credentialsFile: 'oauth_creds.json',
scope: ['https://www.googleapis.com/auth/cloud-platform'],
logPrefix: '[Antigravity Auth]'
}
};
/**
* 活动的服务器实例管理
*/
const activeServers = new Map();
/**
* 活动的轮询任务管理
*/
const activePollingTasks = new Map();
/**
* Qwen OAuth 配置
*/
const QWEN_OAUTH_CONFIG = {
clientId: 'f0304373b74a44d2b584a3fb70ca9e56',
scope: 'openid profile email model.completion',
deviceCodeEndpoint: 'https://chat.qwen.ai/api/v1/oauth2/device/code',
tokenEndpoint: 'https://chat.qwen.ai/api/v1/oauth2/token',
grantType: 'urn:ietf:params:oauth:grant-type:device_code',
credentialsDir: '.qwen',
credentialsFile: 'oauth_creds.json',
logPrefix: '[Qwen Auth]'
};
/**
* Kiro OAuth 配置(支持多种认证方式)
*/
const KIRO_OAUTH_CONFIG = {
// Kiro Auth Service 端点 (用于 Social Auth)
authServiceEndpoint: 'https://prod.us-east-1.auth.desktop.kiro.dev',
// AWS SSO OIDC 端点 (用于 Builder ID)
ssoOIDCEndpoint: 'https://oidc.us-east-1.amazonaws.com',
// AWS Builder ID 起始 URL
builderIDStartURL: 'https://view.awsapps.com/start',
// 本地回调端口范围(用于 Social Auth HTTP 回调)
callbackPortStart: 19876,
callbackPortEnd: 19880,
// 超时配置
authTimeout: 10 * 60 * 1000, // 10 分钟
pollInterval: 5000, // 5 秒
// CodeWhisperer Scopes
scopes: [
'codewhisperer:completions',
'codewhisperer:analysis',
'codewhisperer:conversations',
'codewhisperer:transformations',
'codewhisperer:taskassist'
],
// 凭据存储(符合现有规范)
credentialsDir: '.kiro',
credentialsFile: 'oauth_creds.json',
// 日志前缀
logPrefix: '[Kiro Auth]'
};
/**
* 活动的 Kiro 回调服务器管理
*/
const activeKiroServers = new Map();
/**
* 活动的 Kiro 轮询任务管理(用于 Builder ID Device Code)
*/
const activeKiroPollingTasks = new Map();
/**
* 生成 HTML 响应页面
* @param {boolean} isSuccess - 是否成功
* @param {string} message - 显示消息
* @returns {string} HTML 内容
*/
function generateResponsePage(isSuccess, message) {
const title = isSuccess ? '授权成功!' : '授权失败';
return `
${title}
`;
}
/**
* 关闭指定端口的活动服务器
* @param {number} port - 端口号
* @returns {Promise}
*/
async function closeActiveServer(port) {
const existingServer = activeServers.get(port);
if (existingServer && existingServer.listening) {
return new Promise((resolve) => {
existingServer.close(() => {
activeServers.delete(port);
console.log(`[OAuth] 已关闭端口 ${port} 上的旧服务器`);
resolve();
});
});
}
}
/**
* 创建 OAuth 回调服务器
* @param {Object} config - OAuth 提供商配置
* @param {string} redirectUri - 重定向 URI
* @param {OAuth2Client} authClient - OAuth2 客户端
* @param {string} credPath - 凭据保存路径
* @param {string} provider - 提供商标识
* @returns {Promise} HTTP 服务器实例
*/
async function createOAuthCallbackServer(config, redirectUri, authClient, credPath, provider, options = {}) {
// 先关闭该端口上的旧服务器
await closeActiveServer(config.port);
return new Promise((resolve, reject) => {
const server = http.createServer(async (req, res) => {
try {
const url = new URL(req.url, redirectUri);
const code = url.searchParams.get('code');
const errorParam = url.searchParams.get('error');
if (code) {
console.log(`${config.logPrefix} 收到来自 Google 的成功回调: ${req.url}`);
try {
const { tokens } = await authClient.getToken(code);
let finalCredPath = credPath;
// 如果指定了保存到 configs 目录
if (options.saveToConfigs) {
const providerDir = options.providerDir;
const targetDir = path.join(process.cwd(), 'configs', providerDir);
await fs.promises.mkdir(targetDir, { recursive: true });
const timestamp = Date.now();
const filename = `${timestamp}_oauth_creds.json`;
finalCredPath = path.join(targetDir, filename);
}
await fs.promises.mkdir(path.dirname(finalCredPath), { recursive: true });
await fs.promises.writeFile(finalCredPath, JSON.stringify(tokens, null, 2));
console.log(`${config.logPrefix} 新令牌已接收并保存到文件: ${finalCredPath}`);
const relativePath = path.relative(process.cwd(), finalCredPath);
// 广播授权成功事件
broadcastEvent('oauth_success', {
provider: provider,
credPath: finalCredPath,
relativePath: relativePath,
timestamp: new Date().toISOString()
});
// 自动关联新生成的凭据到 Pools
await autoLinkProviderConfigs(CONFIG);
res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
res.end(generateResponsePage(true, '您可以关闭此页面'));
} catch (tokenError) {
console.error(`${config.logPrefix} 获取令牌失败:`, tokenError);
res.writeHead(500, { 'Content-Type': 'text/html; charset=utf-8' });
res.end(generateResponsePage(false, `获取令牌失败: ${tokenError.message}`));
} finally {
server.close(() => {
activeServers.delete(config.port);
});
}
} else if (errorParam) {
const errorMessage = `授权失败。Google 返回错误: ${errorParam}`;
console.error(`${config.logPrefix}`, errorMessage);
res.writeHead(400, { 'Content-Type': 'text/html; charset=utf-8' });
res.end(generateResponsePage(false, errorMessage));
server.close(() => {
activeServers.delete(config.port);
});
} else {
console.log(`${config.logPrefix} 忽略无关请求: ${req.url}`);
res.writeHead(204);
res.end();
}
} catch (error) {
console.error(`${config.logPrefix} 处理回调时出错:`, error);
res.writeHead(500, { 'Content-Type': 'text/html; charset=utf-8' });
res.end(generateResponsePage(false, `服务器错误: ${error.message}`));
if (server.listening) {
server.close(() => {
activeServers.delete(config.port);
});
}
}
});
server.on('error', (err) => {
if (err.code === 'EADDRINUSE') {
console.error(`${config.logPrefix} 端口 ${config.port} 已被占用`);
reject(new Error(`端口 ${config.port} 已被占用`));
} else {
console.error(`${config.logPrefix} 服务器错误:`, err);
reject(err);
}
});
const host = 'localhost';
server.listen(config.port, host, () => {
console.log(`${config.logPrefix} OAuth 回调服务器已启动于 ${host}:${config.port}`);
activeServers.set(config.port, server);
resolve(server);
});
});
}
/**
* 处理 Google OAuth 授权(通用函数)
* @param {string} providerKey - 提供商键名
* @param {Object} currentConfig - 当前配置对象
* @param {Object} options - 额外选项
* @returns {Promise