import { Router } from 'express'; import https from 'https'; import crypto from 'crypto'; import fs from 'fs'; import path from 'path'; import logger from '../utils/logger.js'; const router = Router(); const CLIENT_ID = '1071006060591-tmhssin2h21lcre235vtolojh4g403ep.apps.googleusercontent.com'; const CLIENT_SECRET = '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'; const ACCOUNTS_FILE = path.join(process.cwd(), 'data', 'accounts.json'); const SCOPES = [ 'https://www.googleapis.com/auth/cloud-platform', 'https://www.googleapis.com/auth/userinfo.email', 'https://www.googleapis.com/auth/userinfo.profile', 'https://www.googleapis.com/auth/cclog', 'https://www.googleapis.com/auth/experimentsandconfigs' ]; // 存储 state 用于验证 const pendingStates = new Map(); // 获取回调基础 URL function getBaseUrl(req) { // 优先使用环境变量 if (process.env.OAUTH_CALLBACK_URL) { return process.env.OAUTH_CALLBACK_URL; } // 从请求中推断 const protocol = req.headers['x-forwarded-proto'] || req.protocol || 'http'; const host = req.headers['x-forwarded-host'] || req.headers.host; return `${protocol}://${host}`; } // 生成授权 URL router.get('/login', (req, res) => { const state = crypto.randomUUID(); const baseUrl = getBaseUrl(req); const redirectUri = `${baseUrl}/oauth-callback`; // 保存 state,5分钟过期 pendingStates.set(state, { redirectUri, timestamp: Date.now() }); setTimeout(() => pendingStates.delete(state), 5 * 60 * 1000); const params = new URLSearchParams({ access_type: 'offline', client_id: CLIENT_ID, prompt: 'consent', redirect_uri: redirectUri, response_type: 'code', scope: SCOPES.join(' '), state: state }); const authUrl = `https://accounts.google.com/o/oauth2/v2/auth?${params.toString()}`; // 返回 HTML 页面,用户点击后跳转 res.send(`
' + error + '
'); } if (!code) { return res.send('未收到授权码
'); } // 验证 state const stateData = pendingStates.get(state); if (!stateData) { logger.warn('无效的 state 参数,可能是过期或重复使用'); // 尝试从当前请求推断 redirect_uri } const redirectUri = stateData?.redirectUri || `${getBaseUrl(req)}/oauth-callback`; pendingStates.delete(state); try { logger.info('收到授权码,正在交换 Token...'); const tokenData = await exchangeCodeForToken(code, redirectUri); const account = { access_token: tokenData.access_token, refresh_token: tokenData.refresh_token, expires_in: tokenData.expires_in, timestamp: Date.now() }; // 保存到 accounts.json let accounts = []; try { if (fs.existsSync(ACCOUNTS_FILE)) { accounts = JSON.parse(fs.readFileSync(ACCOUNTS_FILE, 'utf-8')); } } catch (err) { logger.warn('读取 accounts.json 失败,将创建新文件'); } accounts.push(account); const dir = path.dirname(ACCOUNTS_FILE); if (!fs.existsSync(dir)) { fs.mkdirSync(dir, { recursive: true }); } fs.writeFileSync(ACCOUNTS_FILE, JSON.stringify(accounts, null, 2)); logger.info(`Token 已保存,当前共 ${accounts.length} 个账号`); res.send(`${err.message}
`); } }); // 交换授权码获取 Token function exchangeCodeForToken(code, redirectUri) { return new Promise((resolve, reject) => { const postData = new URLSearchParams({ code: code, client_id: CLIENT_ID, client_secret: CLIENT_SECRET, redirect_uri: redirectUri, grant_type: 'authorization_code' }).toString(); const options = { hostname: 'oauth2.googleapis.com', path: '/token', method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded', 'Content-Length': Buffer.byteLength(postData) } }; const req = https.request(options, (res) => { let body = ''; res.on('data', chunk => body += chunk); res.on('end', () => { if (res.statusCode === 200) { resolve(JSON.parse(body)); } else { reject(new Error(`HTTP ${res.statusCode}: ${body}`)); } }); }); req.on('error', reject); req.write(postData); req.end(); }); } export default router;