'use strict'; const crypto = require('crypto'); const fetch = require('node-fetch'); const { URLSearchParams } = require('url'); // ─── 常量 ─── const OWNER_EMAIL = (process.env.ZY_OWNER_EMAIL || '565183519@qq.com').trim().toLowerCase(); const GITHUB_CLIENT_ID = process.env.GITHUB_CLIENT_ID; const GITHUB_CLIENT_SECRET = process.env.GITHUB_CLIENT_SECRET; const GITHUB_REDIRECT_URI = process.env.GITHUB_REDIRECT_URI || 'http://localhost:3800/api/oauth/github/callback'; const NOTION_CLIENT_ID = process.env.NOTION_CLIENT_ID; const NOTION_CLIENT_SECRET = process.env.NOTION_CLIENT_SECRET; const NOTION_REDIRECT_URI = process.env.NOTION_REDIRECT_URI || 'http://localhost:3800/api/oauth/notion/callback'; const STATE_SECRET = process.env.OAUTH_STATE_SECRET || 'default-secret-change-me'; // ─── 状态令牌生成 ─── function generateStateToken(email) { const hash = crypto.createHmac('sha256', STATE_SECRET) .update(email) .digest('hex'); return `${hash}:${Date.now()}`; } function verifyStateToken(token, email) { const [hash, timestamp] = token.split(':'); if (!hash || !timestamp) return false; // 状态令牌10分钟过期 if (Date.now() - Number(timestamp) > 10 * 60 * 1000) return false; const expectedHash = crypto.createHmac('sha256', STATE_SECRET) .update(email) .digest('hex'); return hash === expectedHash; } // ─── GitHub OAuth ─── async function getGitHubOAuthUrl(email) { if (email.toLowerCase() !== OWNER_EMAIL) { throw new Error('仅限主权者邮箱使用'); } const state = generateStateToken(email); const params = new URLSearchParams({ client_id: GITHUB_CLIENT_ID, redirect_uri: GITHUB_REDIRECT_URI, scope: 'user:email', state }); return `https://github.com/login/oauth/authorize?${params.toString()}`; } async function handleGitHubCallback(code, state) { if (!verifyStateToken(state, OWNER_EMAIL)) { throw new Error('无效的状态令牌'); } // 交换access token const tokenParams = new URLSearchParams({ client_id: GITHUB_CLIENT_ID, client_secret: GITHUB_CLIENT_SECRET, code, redirect_uri: GITHUB_REDIRECT_URI }); const tokenRes = await fetch('https://github.com/login/oauth/access_token', { method: 'POST', headers: { 'Accept': 'application/json', 'Content-Type': 'application/x-www-form-urlencoded' }, body: tokenParams }); const tokenData = await tokenRes.json(); if (tokenData.error) { throw new Error(`GitHub OAuth错误: ${tokenData.error_description || tokenData.error}`); } // 返回与验证码登录相同的session token结构 return { token: crypto.randomBytes(32).toString('hex'), expiresAt: Date.now() + 7 * 24 * 60 * 60 * 1000, // 7天 provider: 'github', email: OWNER_EMAIL }; } // ─── Notion OAuth ─── async function getNotionOAuthUrl(email) { if (email.toLowerCase() !== OWNER_EMAIL) { throw new Error('仅限主权者邮箱使用'); } const state = generateStateToken(email); const params = new URLSearchParams({ client_id: NOTION_CLIENT_ID, redirect_uri: NOTION_REDIRECT_URI, response_type: 'code', owner: 'user', state }); return `https://api.notion.com/v1/oauth/authorize?${params.toString()}`; } async function handleNotionCallback(code, state) { if (!verifyStateToken(state, OWNER_EMAIL)) { throw new Error('无效的状态令牌'); } // 交换access token const tokenRes = await fetch('https://api.notion.com/v1/oauth/token', { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': `Basic ${Buffer.from(`${NOTION_CLIENT_ID}:${NOTION_CLIENT_SECRET}`).toString('base64')}` }, body: JSON.stringify({ grant_type: 'authorization_code', code, redirect_uri: NOTION_REDIRECT_URI }) }); const tokenData = await tokenRes.json(); if (tokenData.error) { throw new Error(`Notion OAuth错误: ${tokenData.error_description || tokenData.error}`); } // 返回与验证码登录相同的session token结构 return { token: crypto.randomBytes(32).toString('hex'), expiresAt: Date.now() + 7 * 24 * 60 * 60 * 1000, // 7天 provider: 'notion', email: OWNER_EMAIL }; } module.exports = { getGitHubOAuthUrl, handleGitHubCallback, getNotionOAuthUrl, handleNotionCallback };