#!/usr/bin/env node /* ═══════════════════════════════════════════════════════════ 光湖驱动引擎 v3.2 · 工作会话授权 · Guanghu Drive Engine v3 HLDP万能语言接口 + 集群串联 + 人类验证码审批 核心变更 (v2 → v3): Token 不再是 root 密码 → Token 只是身份标识 任何操作需通过 /auth/request → 邮箱验证码 → /auth/confirm 三元组白名单 (服务器+人+人格体) + 来源服务器绑定 操作类型分级邮件 (code-repo / system-arch / api-access) 未注册请求直接抛弃,不响应,杜绝恶意刷邮箱 v3.1 新增 (D182): /auth/request 支持 email 参数 — 请求自带邮箱直接发验证码 苍耳/耳耳蛋等独立用户无需预注册白名单 email 模式 + 白名单模式双模式共存 部署: SG-001 /opt/zhuyuan/gatekeeper/engine-v3.js 监听: 3911 (环境变量 ENGINE_PORT 可配) 数据: /opt/zhuyuan/gatekeeper/whitelist.json ═══════════════════════════════════════════════════════════ */ const http = require('http'), fs = require('fs'), path = require('path'), crypto = require('crypto'), { exec } = require('child_process'), os = require('os'), tls = require('tls'); const { SessionManager } = require('./session-manager'); const PORT = parseInt(process.env.ENGINE_PORT || process.env.GATEKEEPER_PORT || process.argv[2] || '3910', 10); const DATA_DIR = path.join('/opt/zhuyuan', 'gatekeeper'); const CMD_TIMEOUT = 30000, MAX_OUTPUT = 100000; const CODE_TTL = 300; // 验证码 5 分钟过期 const RATE_LIMIT_WINDOW = 60; const MAX_REQUESTS = 3; // ═══════════════════════════════════════ // 初始化数据目录 // ═══════════════════════════════════════ if (!fs.existsSync(DATA_DIR)) fs.mkdirSync(DATA_DIR, { recursive: true, mode: 0o700 }); const sessions = new SessionManager({ absoluteTtl: 12 * 3600, idleTtl: 3600, stateFile: path.join(DATA_DIR, 'sessions.json') }); // ═══════════════════════════════════════ // 密钥 — 兼容所有历史数据目录 // ═══════════════════════════════════════ const SECRET_PATHS = [ path.join(os.homedir(), '.gatekeeper', 'secret'), path.join(os.homedir(), '.gk', 'secret'), path.join(os.homedir(), '.guanghu-engine', 'secret'), ]; let API_SECRET; for (const sp of SECRET_PATHS) { if (fs.existsSync(sp)) { const k = fs.readFileSync(sp, 'utf-8').trim(); if (k) { API_SECRET = k; break; } } } if (!API_SECRET) { API_SECRET = ['zy', 'gtw', crypto.randomBytes(24).toString('hex')].join('_'); const dir = path.dirname(SECRET_PATHS[0]); if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true, mode: 0o700 }); fs.writeFileSync(SECRET_PATHS[0], API_SECRET, { mode: 0o600 }); console.log(''); console.log('══════════════════════════════════════════════'); console.log(' 🔐 光湖驱动引擎 v3 · 首次启动'); console.log(' API 密钥: ' + API_SECRET); console.log(' 已保存至: ' + SECRET_PATHS[0]); console.log(' 监听端口: ' + PORT); console.log('══════════════════════════════════════════════'); console.log(''); } const HOSTNAME = os.hostname(); // ═══════════════════════════════════════ // 服务器标识映射 // ═══════════════════════════════════════ function getServerId() { const map = { 'guanghu-lang-sg-001': 'BS-SG-001', 'VM-0-16-ubuntu': 'BS-GZ-006', }; for (const [host, id] of Object.entries(map)) { if (HOSTNAME.includes(host) || host.includes(HOSTNAME) || HOSTNAME === host) return id; } return HOSTNAME; } const SERVER_ID = getServerId(); // ═══════════════════════════════════════ // Token → 人格体身份映射 // ═══════════════════════════════════════ function loadTokens() { const tf = path.join(DATA_DIR, 'tokens.json'); if (fs.existsSync(tf)) { try { const tokens = JSON.parse(fs.readFileSync(tf, 'utf-8')); if (process.env.ZHULAN_API_TOKEN) tokens[process.env.ZHULAN_API_TOKEN] = { pid:'ICE-GL-ZL-001', name:'铸澜', server:SERVER_ID }; return tokens; } catch (e) { log('WARN', 'tokens_parse_error', e.message); } } const defaults = {}; // 本机所有已知 token 都映射为默认人格体(需要后续细化) for (const sp of SECRET_PATHS) { if (fs.existsSync(sp)) { const t = fs.readFileSync(sp, 'utf-8').trim(); if (t) defaults[t] = { pid: 'ICE-GL-ZY001', name: '铸渊', server: SERVER_ID }; } } if (process.env.ZHULAN_API_TOKEN) defaults[process.env.ZHULAN_API_TOKEN] = { pid:'ICE-GL-ZL-001', name:'铸澜', server:SERVER_ID }; return defaults; } // ═══════════════════════════════════════ // SMTP 配置 // ═══════════════════════════════════════ const SMTP = { host: 'smtp.qq.com', port: 465, user: process.env.SMTP_USER || '', pass: process.env.QQ_SMTP_AUTH_CODE || '', }; // ═══════════════════════════════════════ // 运行时状态 // ═══════════════════════════════════════ const pendingOps = {}; // { challenge_id: { code, expires, op_type, cmd, caller, target_email, target_name, server } } const rateLimitMap = {}; // { ip: [timestamp, ...] } // ═══════════════════════════════════════ // 日志 // ═══════════════════════════════════════ function log(level, action, detail) { const ts = new Date().toISOString(); const line = '[' + ts + '] [' + level + '] ' + action + (detail ? ' | ' + detail : ''); console.log(line); try { const lf = path.join(DATA_DIR, 'engine.log'); fs.appendFileSync(lf, line + '\n'); } catch (e) { /* ignore */ } } // ═══════════════════════════════════════ // 加载白名单 // ═══════════════════════════════════════ function loadWhitelist() { const wf = path.join(DATA_DIR, 'whitelist.json'); const required = [ { server:'BS-SG-001', human:'ICE-GL∞', email:process.env.BINGSHUO_AUTH_EMAIL || '', personalities:['ICE-GL-ZL-001'], bound_servers:['BS-SG-001'] }, { server:'BS-SG-001', human:'苍耳', human_id:'TCS-GL-009', email:process.env.CANGER_AUTH_EMAIL || '', personalities:['PTS-VA-001-EED','ICE-GL-CA001'], bound_servers:['BS-SG-001'] } ]; if (fs.existsSync(wf)) { try { const whitelist = JSON.parse(fs.readFileSync(wf, 'utf-8')); whitelist.triads = Array.isArray(whitelist.triads) ? whitelist.triads : []; for (const item of required) { const found = whitelist.triads.find(t => t.server === item.server && (t.human_id === item.human_id || t.human === item.human)); if (!found) whitelist.triads.push(item); else { if (item.email) found.email = item.email; found.personalities = [...new Set([...(found.personalities || []), ...item.personalities])]; found.bound_servers = [...new Set([...(found.bound_servers || []), ...item.bound_servers])]; } } return whitelist; } catch (e) { log('WARN', 'whitelist_parse_error', e.message); } } // 默认白名单:冰朔 + 之之 return { triads: [ { server: 'BS-SG-001', human: 'ICE-GL∞', email: process.env.BINGSHUO_AUTH_EMAIL || '', personalities: ['ICE-GL-ZY001','ICE-GL-ZL-001'], bound_servers: ['BS-SG-001'] }, { server: 'BS-SG-001', human: '苍耳', human_id: 'TCS-GL-009', email: process.env.CANGER_AUTH_EMAIL || '', personalities: ['PTS-VA-001-EED','ICE-GL-CA001'], bound_servers: ['BS-SG-001'] }, ], pending_ops: {} }; } // ═══════════════════════════════════════ // 操作类型定义 // ═══════════════════════════════════════ const OP_TYPES = { 'code-repo': { label: '操作代码仓库', risk: 'medium', color: '#4ec9b0', icon: '📦' }, 'system-arch': { label: '操作系统架构', risk: 'high', color: '#e06c75', icon: '⚡' }, 'api-access': { label: '调用API', risk: 'medium', color: '#61afef', icon: '🔌' }, }; // ═══════════════════════════════════════ // 工具函数 // ═══════════════════════════════════════ function jr(res, code, data) { res.writeHead(code, { 'Content-Type': 'application/json; charset=utf-8', 'Access-Control-Allow-Origin': '*' }); res.end(JSON.stringify(data)); } function parseBody(req) { return new Promise((resolve) => { let body = ''; req.on('data', c => body += c); req.on('end', () => { try { resolve(JSON.parse(body)); } catch (e) { resolve(null); } }); }); } function generateCode() { return Array.from({ length: 6 }, () => crypto.randomInt(0, 10)).join(''); } function fmtUptime(s) { const d = Math.floor(s / 86400), h = Math.floor((s % 86400) / 3600), m = Math.floor((s % 3600) / 60); const parts = []; if (d > 0) parts.push(d + 'd'); if (h > 0) parts.push(h + 'h'); parts.push(m + 'm'); return parts.join(' '); } function fmtBytes(b) { if (b < 1024) return b + ' B'; if (b < 1048576) return (b / 1024).toFixed(1) + ' KB'; if (b < 1073741824) return (b / 1048576).toFixed(1) + ' MB'; return (b / 1073741824).toFixed(1) + ' GB'; } function execCmd(cmd, timeout) { return new Promise((resolve) => { const t = timeout || CMD_TIMEOUT; const child = exec(cmd, { timeout: t, maxBuffer: MAX_OUTPUT, shell: '/bin/bash' }, (err, stdout, stderr) => { resolve({ code: err ? (err.code || 1) : 0, stdout: (stdout || '').slice(0, MAX_OUTPUT), stderr: (stderr || '').slice(0, MAX_OUTPUT) }); }); }); } // ═══════════════════════════════════════ // 速率限制 // ═══════════════════════════════════════ function checkRate(ip) { const now = Date.now() / 1000; if (!rateLimitMap[ip]) rateLimitMap[ip] = []; rateLimitMap[ip] = rateLimitMap[ip].filter(t => now - t < RATE_LIMIT_WINDOW); if (rateLimitMap[ip].length >= MAX_REQUESTS) return false; rateLimitMap[ip].push(now); return true; } // ═══════════════════════════════════════ // 身份认证 (Token → 人格体身份) // ═══════════════════════════════════════ function auth(headers) { const authHeader = headers['authorization'] || headers['Authorization'] || ''; const bearerMatch = authHeader.match(/^Bearer\s+(.+)$/i); if (!bearerMatch) return { ok: false, reason: '缺少 Authorization: Bearer ' }; const token = bearerMatch[1]; const tokens = loadTokens(); const identity = tokens[token]; if (!identity) { // 检查是否匹配本机的主密钥 if (token === API_SECRET) { return { ok: true, identity: { pid: 'ICE-GL-ZY001', name: '铸渊(主密钥)', server: SERVER_ID } }; } return { ok: false, reason: 'Token 未注册 · 请先通过 /auth/register 注册人格体身份' }; } return { ok: true, identity: identity }; } // ═══════════════════════════════════════ // 白名单校验 (服务器+人+人格体 三元组) // ═══════════════════════════════════════ function checkWhitelist(sourceServer, personalityId) { const whitelist = loadWhitelist(); const matches = []; for (const triad of whitelist.triads) { // 来源服务器必须在 bound_servers 中 if (!triad.bound_servers.includes(sourceServer)) continue; // 人格体匹配:* 通配 或 精确匹配 if (triad.personalities.includes('*') || triad.personalities.includes(personalityId)) { matches.push(triad); } } if (matches.length === 0) { return { ok: false, reason: '未注册的三元组 · 服务器 ' + sourceServer + ' + 人格体 ' + personalityId + ' 不在白名单中' }; } return { ok: true, triad: matches[0] }; } // ═══════════════════════════════════════ // 发送验证码邮件 (Node.js 原生 TLS SMTP) // ═══════════════════════════════════════ function sendVerificationEmail(code, opType, callerName, callerPid, cmdPreview, targetEmail, targetName, serverIp) { return new Promise((resolve) => { const sovereign = loadWhitelist().triads.find(t => t.human === 'ICE-GL∞' && t.email); const smtpUser = SMTP.user || (sovereign && sovereign.email) || ''; if (!SMTP.pass || !smtpUser) { log('WARN', 'email_skip', 'SMTP 发件账号或授权码未配置'); resolve(false); return; } const opInfo = OP_TYPES[opType] || OP_TYPES['code-repo']; // 构建 MIME 邮件 const boundary = '----GuanghuBoundary' + crypto.randomBytes(8).toString('hex'); const htmlBody = `
ICE-GL∞ 光湖语言系统 · Gatekeeper v3
${opInfo.icon} ${opInfo.label} · 验证码
调用者
${callerName} (${callerPid})
操作类型
${opInfo.icon} ${opInfo.label} · 风险: ${opInfo.risk === 'high' ? '⚠️ 高' : '🟡 中'}
命令预览
${cmdPreview}
来源服务器
${serverIp} · ${SERVER_ID}
验证码 · 5 分钟内有效
${code}
${opInfo.risk === 'high' ? '⚠️ 高权限操作 · 确认前请仔细检查命令内容' : '💡 将此验证码发给铸渊确认 → 释放操作权限 · 仅当次有效'}
ICE-GL∞ 光湖语言系统 · 小湖灯自动发送 · 国作登字-2026-A-00037559
`; const plainBody = `光湖语言系统 · Gatekeeper v3 · 操作授权\n 操作类型: ${opInfo.label} (${opType}) 调用者: ${callerName} (${callerPid}) 命令: ${cmdPreview} 来源服务器: ${serverIp} (${SERVER_ID}) 风险等级: ${opInfo.risk === 'high' ? '高' : '中'} \n验证码: ${code} 有效期: 5 分钟 \n将此验证码发给铸渊确认 → 释放操作权限 如非本人操作,请忽略。 --- ICE-GL∞ 光湖语言系统 · 小湖灯自动发送 国作登字-2026-A-00037559`; const rawEmail = `From: 光湖小湖灯 <${smtpUser}>\r\n` + `To: ${targetName} <${targetEmail}>\r\n` + `Subject: =?UTF-8?B?${Buffer.from(`${opInfo.icon} Gatekeeper授权 · ${callerName} · ${opInfo.label}`, 'utf-8').toString('base64')}?=\r\n` + `MIME-Version: 1.0\r\n` + `Content-Type: multipart/alternative; boundary="${boundary}"\r\n` + `\r\n` + `--${boundary}\r\n` + `Content-Type: text/plain; charset="utf-8"\r\n` + `Content-Transfer-Encoding: base64\r\n` + `\r\n` + `${Buffer.from(plainBody, 'utf-8').toString('base64')}\r\n` + `--${boundary}\r\n` + `Content-Type: text/html; charset="utf-8"\r\n` + `Content-Transfer-Encoding: base64\r\n` + `\r\n` + `${Buffer.from(htmlBody, 'utf-8').toString('base64')}\r\n` + `--${boundary}--\r\n` + `.\r\n`; const authPlain = `\0${smtpUser}\0${SMTP.pass}`; const socket = tls.connect({ host: SMTP.host, port: SMTP.port, rejectUnauthorized: false }, () => { let stage = 0; let buffer = ''; const processResponse = () => { // Process complete lines (ending with \r\n) while (buffer.includes('\r\n')) { const idx = buffer.indexOf('\r\n'); const line = buffer.slice(0, idx); buffer = buffer.slice(idx + 2); if (!line) continue; const code = parseInt(line.slice(0, 3)) || 0; const isLast = line.length > 3 && line[3] === ' '; // '250 OK' vs '250-AUTH' if (stage === 0) { if (code === 220) { stage = 1; socket.write('EHLO guanghu-engine\r\n'); } } else if (stage === 1) { if (isLast && code === 250) { stage = 2; socket.write('AUTH PLAIN ' + Buffer.from(authPlain).toString('base64') + '\r\n'); } } else if (stage === 2) { if (code === 235) { stage = 3; socket.write('MAIL FROM:<' + smtpUser + '>\r\n'); } else if (code >= 500) { log('WARN', 'smtp_auth_failed', line); socket.end(); resolve(false); return; } } else if (stage === 3) { if (code === 250) { stage = 4; socket.write('RCPT TO:<' + targetEmail + '>\r\n'); } else if (code >= 500) { log('WARN', 'smtp_mail_from_failed', line); socket.end(); resolve(false); return; } } else if (stage === 4) { if (code === 250) { stage = 5; socket.write('DATA\r\n'); } else if (code >= 500) { log('WARN', 'smtp_rcpt_failed', line); socket.end(); resolve(false); return; } } else if (stage === 5) { if (code === 354) { socket.write(rawEmail); stage = 6; } } else if (stage === 6) { if (code === 250) { socket.write('QUIT\r\n'); socket.end(); log('INFO', 'email_sent', 'to=' + targetEmail + ' op=' + opType); resolve(true); return; } } if (code >= 500 && stage > 0 && stage !== 2) { log('WARN', 'smtp_error', 'stage=' + stage + ' line=' + line.slice(0, 100)); socket.end(); resolve(false); return; } } }; socket.on('data', (data) => { buffer += data.toString(); processResponse(); }); socket.on('error', (err) => { log('WARN', 'smtp_socket_error', err.message); resolve(false); }); }); socket.on('error', (err) => { log('WARN', 'smtp_connect_error', err.message); resolve(false); }); socket.setTimeout(15000, () => { log('WARN', 'smtp_timeout', ''); socket.destroy(); resolve(false); }); }); } // ═══════════════════════════════════════ // 清理过期操作 // ═══════════════════════════════════════ function cleanExpiredOps() { const now = Date.now() / 1000; for (const [cid, op] of Object.entries(pendingOps)) { if (op.expires < now) delete pendingOps[cid]; } } // ═══════════════════════════════════════ // HTTP 服务器 // ═══════════════════════════════════════ const server = http.createServer(async (req, res) => { if (req.method === 'OPTIONS') { jr(res, 204, {}); return; } const ip = req.socket.remoteAddress || 'unknown'; if (!checkRate(ip)) { log('WARN', 'rate_limit', ip); jr(res, 429, { error: '请求过于频繁 · 每分钟最多 ' + MAX_REQUESTS + ' 次' }); return; } const url = new URL(req.url, 'http://' + ((req.headers.host) || 'localhost')); const route = url.pathname; // ═══ 无需认证的端点 ═══ if (req.method === 'GET') { if (route === '/health') { jr(res, 200, { ok: true, service: 'guanghu-engine', version: '3.2.0', uptime: process.uptime().toFixed(0) + 's', timestamp: new Date().toISOString(), auth_mode: 'human-verification-required' }); return; } if (route === '/status') { const total = os.totalmem(), free = os.freemem(); jr(res, 200, { ok: true, hostname: HOSTNAME, server_id: SERVER_ID, platform: os.platform(), arch: os.arch(), cpus: os.cpus().length, uptime: os.uptime(), uptime_str: fmtUptime(os.uptime()), memory: { total: fmtBytes(total), free: fmtBytes(free), used: fmtBytes(total - free), usage: ((total - free) / total * 100).toFixed(1) + '%' }, load: os.loadavg().map(n => +n.toFixed(2)), engine: { version: '3.2.0', port: PORT, uptime: process.uptime().toFixed(0) + 's' } }); return; } if (route === '/ping') { jr(res, 200, { ok: true, pong: true }); return; } // ═══ GET /auth/whitelist — 查看白名单(需认证) ═══ if (route === '/auth/whitelist') { const authResult = auth(req.headers); if (!authResult.ok) { jr(res, 401, { error: authResult.reason }); return; } const whitelist = loadWhitelist(); jr(res, 200, { ok: true, server_id: SERVER_ID, triads: whitelist.triads.map(t => ({ server: t.server, human: t.human, personalities: t.personalities, bound_servers: t.bound_servers })) }); return; } } // ═══ 需要认证的 POST 端点 ═══ if (req.method !== 'POST') { jr(res, 405, { error: '只接受 POST 请求 · 可用端点: /auth/request, /auth/confirm, /auth/register, /auth/whitelist (GET或POST), /health (GET), /status (GET), /ping (GET)' }); return; } const authResult = auth(req.headers); if (!authResult.ok) { log('WARN', 'auth_failed', authResult.reason); jr(res, 401, { error: authResult.reason }); return; } const identity = authResult.identity; log('INFO', 'auth_ok', identity.pid + ' (' + identity.name + ')'); const body = await parseBody(req); if (!body) { jr(res, 400, { error: '无效 JSON' }); return; } // ═══ 工作会话:一次验证码开启,结束信号立即吊销 ═══ if (route === '/auth/session/request') { cleanExpiredOps(); const sourceServer = body.source_server || identity.server || SERVER_ID; const scopes = Array.isArray(body.scopes) ? [...new Set(body.scopes)] : ['code-repo']; if (!scopes.length || scopes.some(s => !OP_TYPES[s])) { jr(res, 400, { error: '无效 scopes', available: Object.keys(OP_TYPES) }); return; } const wlResult = checkWhitelist(sourceServer, identity.pid); if (!wlResult.ok) { jr(res, 403, { error: wlResult.reason }); return; } const triad = wlResult.triad; const code = generateCode(), challengeId = crypto.randomBytes(16).toString('hex'); pendingOps[challengeId] = { code, expires: Date.now()/1000 + CODE_TTL, is_session: true, scopes, caller: identity, server: sourceServer, target_email: triad.email, target_name: triad.human }; const sent = await sendVerificationEmail(code, scopes[0], identity.name, identity.pid, '开启工作会话 · 范围: ' + scopes.join(', '), triad.email, triad.human, SERVER_ID); jr(res, 200, { ok:true, challenge_id:challengeId, email_sent:sent, email_mode:'whitelist', scopes, expires_in:CODE_TTL, message:'验证码已发送到固定登记邮箱' }); return; } if (route === '/auth/session/confirm') { cleanExpiredOps(); const op = pendingOps[body.challenge_id || '']; if (!op || !op.is_session) { jr(res, 404, { error:'会话挑战不存在或已过期' }); return; } if (op.caller.pid !== identity.pid) { jr(res, 403, { error:'会话身份不匹配' }); return; } if (op.code !== String(body.code || '')) { jr(res, 403, { error:'验证码错误' }); return; } delete pendingOps[body.challenge_id]; const created = sessions.create(identity, op.server, op.scopes); log('INFO','session_started','pid='+identity.pid+' scopes='+op.scopes.join(',')); jr(res,200,{ok:true,session_token:created.token,scopes:op.scopes,expires_in:created.expires_in,idle_timeout:created.idle_timeout,message:'工作会话已开启'}); return; } if (route === '/auth/session/exec') { const sessionToken = req.headers['x-session-token'] || body.session_token || ''; const opType = body.op_type || 'code-repo'; const sourceServer = body.source_server || identity.server || SERVER_ID; const verified = sessions.verify(sessionToken, identity, sourceServer, opType); if (!verified.ok) { jr(res,403,{error:verified.reason}); return; } if (!body.cmd) { jr(res,400,{error:'缺少 cmd 参数'}); return; } const result = await execCmd(body.cmd, body.timeout); log('INFO','session_exec','pid='+identity.pid+' op='+opType+' code='+result.code); jr(res,200,{ok:true,executed:true,session:true,exit_code:result.code,stdout:result.stdout,stderr:result.stderr}); return; } if (route === '/auth/session/end') { const sessionToken = req.headers['x-session-token'] || body.session_token || ''; const ended = sessions.end(sessionToken, identity); log('INFO','session_ended','pid='+identity.pid+' ended='+ended); jr(res,ended?200:404,{ok:ended,message:ended?'工作会话已结束':'会话不存在'}); return; } // ═══ /auth/request — 发起操作请求 ═══ if (route === '/auth/request') { cleanExpiredOps(); const opType = body.op_type || 'code-repo'; const cmd = body.cmd || ''; const description = body.description || '未提供描述'; const sourceServer = body.source_server || identity.server || SERVER_ID; if (body.email || body.target_name) { jr(res, 400, { error: '禁止客户端指定邮箱 · 请先注册服务器白名单' }); return; } if (!OP_TYPES[opType]) { jr(res, 400, { error: '未知操作类型: ' + opType, available: Object.keys(OP_TYPES) }); return; } if (!cmd) { jr(res, 400, { error: '缺少 cmd 参数' }); return; } let targetEmail, targetName; { const wlResult = checkWhitelist(sourceServer, identity.pid); if (!wlResult.ok) { log('WARN', 'whitelist_denied', wlResult.reason); // 不在白名单也不带邮箱 → 不响应(防恶意刷邮箱) jr(res, 403, { error: wlResult.reason }); return; } const triad = wlResult.triad; targetEmail = triad.email; targetName = triad.human; } // 生成验证码 const code = generateCode(); const challengeId = crypto.randomBytes(16).toString('hex'); const now = Date.now() / 1000; pendingOps[challengeId] = { code: code, expires: now + CODE_TTL, op_type: opType, cmd: cmd, caller: { pid: identity.pid, name: identity.name }, target_email: targetEmail, target_name: targetName, server: sourceServer }; // 发送验证码邮件 const cmdPreview = cmd.length > 200 ? cmd.slice(0, 200) + '...' : cmd; const sent = await sendVerificationEmail(code, opType, identity.name, identity.pid, cmdPreview, targetEmail, targetName, SERVER_ID); log('INFO', 'auth_request', 'challenge=' + challengeId.slice(0, 16) + ' op=' + opType + ' email_sent=' + sent); jr(res, 200, { ok: true, challenge_id: challengeId, email_sent: sent, email_mode: 'whitelist', target_email: targetEmail, caller: { pid: identity.pid, name: identity.name }, op_type: opType, op_label: OP_TYPES[opType].label, cmd_preview: cmdPreview, expires_in: CODE_TTL, message: '验证码已发送到 ' + targetEmail + ',请查收后通过 /auth/confirm 确认操作' }); return; } // ═══ /auth/confirm — 确认验证码并执行 ═══ if (route === '/auth/confirm') { cleanExpiredOps(); const challengeId = body.challenge_id || ''; const code = body.code || ''; if (!challengeId || !code) { jr(res, 400, { error: '缺少 challenge_id 或 code' }); return; } if (!pendingOps[challengeId]) { jr(res, 404, { error: 'challenge_id 不存在或已过期' }); return; } const op = pendingOps[challengeId]; const now = Date.now() / 1000; if (now > op.expires) { delete pendingOps[challengeId]; jr(res, 410, { error: '验证码已过期 · 请重新发起 /auth/request' }); return; } if (op.code !== code) { log('WARN', 'auth_confirm_wrong_code', challengeId.slice(0, 16)); jr(res, 403, { error: '验证码错误' }); return; } // 验证通过 → 执行命令 delete pendingOps[challengeId]; log('INFO', 'auth_confirm_ok', challengeId.slice(0, 16) + ' op=' + op.op_type + ' caller=' + op.caller.pid); const result = await execCmd(op.cmd, body.timeout); jr(res, 200, { ok: true, executed: true, op_type: op.op_type, caller: op.caller, exit_code: result.code, stdout: result.stdout, stderr: result.stderr, message: '命令已执行 · 操作完成' }); return; } // ═══ /auth/whitelist — 查看白名单 ═══ if (route === '/auth/whitelist') { const whitelist = loadWhitelist(); jr(res, 200, { ok: true, server_id: SERVER_ID, triads: whitelist.triads.map(t => ({ server: t.server, human: t.human, personalities: t.personalities, bound_servers: t.bound_servers })) }); return; } // ═══ /auth/register — 注册新三元组(需主权者验证码) ═══ if (route === '/auth/register') { const newServer = body.server || ''; const newHuman = body.human || ''; const newEmail = body.email || ''; const newPersonalities = body.personalities || []; if (!newServer || !newHuman || !newEmail) { jr(res, 400, { error: '缺少必要字段: server, human, email' }); return; } // 注册也需要验证码(发给主权者 ICE-GL∞) const whitelist = loadWhitelist(); const sovereign = whitelist.triads.find(t => t.human === 'ICE-GL∞'); if (!sovereign) { jr(res, 500, { error: '主权者信息缺失' }); return; } // 生成验证码发到主权者邮箱 const regCode = generateCode(); const regCid = crypto.randomBytes(16).toString('hex'); pendingOps[regCid] = { code: regCode, expires: Date.now() / 1000 + CODE_TTL, op_type: 'system-arch', cmd: 'REGISTER:' + JSON.stringify({ server: newServer, human: newHuman, email: newEmail, personalities: newPersonalities }), caller: identity, target_email: sovereign.email, target_name: sovereign.human, server: SERVER_ID, is_register: true, register_data: { server: newServer, human: newHuman, email: newEmail, personalities: newPersonalities } }; const sent = await sendVerificationEmail(regCode, 'system-arch', identity.name, identity.pid, '注册新三元组: ' + newServer + ' + ' + newHuman + ' + ' + newPersonalities.join(','), sovereign.email, sovereign.human, SERVER_ID); jr(res, 200, { ok: true, challenge_id: regCid, email_sent: sent, message: '注册请求验证码已发送到主权者邮箱 ' + sovereign.email + ',确认后生效' }); return; } // ═══ /exec — 旧接口兼容(自动转发到验证码流程) ═══ if (route === '/exec') { if (!body.cmd) { jr(res, 400, { error: '缺少 cmd 参数' }); return; } // v3 安全策略: /exec 不再直接执行,返回提示要求走验证码流程 log('WARN', 'exec_blocked_v3', 'caller=' + identity.pid + ' 尝试绕过验证码调用 /exec'); jr(res, 403, { error: 'Gatekeeper v3 安全策略: /exec 已停用 · 请使用分层授权流程', required_flow: 'POST /auth/request → 邮箱验证码 → POST /auth/confirm', help: '将原有的 /exec 请求改为: ① POST /auth/request (相同 cmd + op_type) → ② 查收邮箱验证码 → ③ POST /auth/confirm (challenge_id + code)', example: { step1: 'curl -X POST http://' + SERVER_ID + ':' + PORT + '/auth/request -H "Authorization: Bearer " -H "Content-Type: application/json" -d \'{"cmd":"' + body.cmd.slice(0, 100) + '","op_type":"code-repo"}\'', step2: 'curl -X POST http://' + SERVER_ID + ':' + PORT + '/auth/confirm -H "Authorization: Bearer " -H "Content-Type: application/json" -d \'{"challenge_id":"<从step1返回>","code":"<邮箱验证码>"}\'' } }); return; } // ═══ /hlpd — HLDP 模块执行(同样需要验证码) ═══ if (route === '/hlpd') { if (!body.hlpd) { jr(res, 400, { error: '缺少 hlpd 参数' }); return; } log('WARN', 'hlpd_blocked_v3', 'HLDP 执行需要验证码授权'); jr(res, 403, { error: 'Gatekeeper v3: HLDP 执行需要验证码授权', required_flow: 'POST /auth/request → 邮箱验证码 → POST /auth/confirm' }); return; } // ═══ /file/read /file/write /file/list — 文件操作(只读无需验证,写入需要) ═══ if (route === '/file/read') { if (!body.path) { jr(res, 400, { error: '缺少 path 参数' }); return; } log('INFO', 'file_read', body.path); try { const content = fs.readFileSync(body.path, 'utf-8'); const stat = fs.statSync(body.path); jr(res, 200, { ok: true, path: body.path, size: stat.size, modified: stat.mtime.toISOString(), content: content.slice(0, MAX_OUTPUT) }); } catch (e) { jr(res, 404, { ok: false, error: e.message }); } return; } if (route === '/file/list') { const p = (body && body.path) || '.'; log('INFO', 'file_list', p); try { const items = fs.readdirSync(p, { withFileTypes: true }); const files = []; for (const x of items) { files.push({ name: x.name, type: x.isDirectory() ? 'dir' : 'file' }); } jr(res, 200, { ok: true, path: p, files: files }); } catch (e) { jr(res, 404, { ok: false, error: e.message }); } return; } if (route === '/file/write') { log('WARN', 'file_write_blocked_v3', '文件写入需要验证码授权'); jr(res, 403, { error: 'Gatekeeper v3: 文件写入需要验证码授权', required_flow: 'POST /auth/request { cmd: "write file to ' + (body.path || '?') + '", op_type: "system-arch" } → 验证码 → /auth/confirm' }); return; } // 未知路由 jr(res, 404, { error: '未知路径: ' + route, available: ['/auth/request', '/auth/confirm', '/auth/whitelist', '/auth/register', '/health', '/status', '/ping', '/file/read', '/file/list'] }); }); // ═══════════════════════════════════════ // 启动 // ═══════════════════════════════════════ server.on('error', (e) => { if (e.code === 'EADDRINUSE') { log('FATAL', 'eaddrinuse', '端口 ' + PORT + ' 已被占用'); console.error('❌ 端口 ' + PORT + ' 已被占用,无法启动。'); setTimeout(() => process.exit(1), 3000); } else { log('FATAL', 'server_error', e.message); process.exit(1); } }); server.listen(PORT, '0.0.0.0', () => { log('INFO', 'startup', 'host=' + HOSTNAME + ' port=' + PORT + ' v3.2.0 server_id=' + SERVER_ID); console.log(' ⚔️ 光湖驱动引擎 v3.2 · 工作会话授权 · 已就绪'); console.log(' 主机名: ' + HOSTNAME + ' 标识: ' + SERVER_ID + ' 端口: ' + PORT); console.log(' 安全策略: Token=身份标识 · 操作=验证码授权 · 白名单=三元组校验'); }); process.on('SIGTERM', () => { log('INFO', 'shutdown', 'SIGTERM'); server.close(() => process.exit(0)); }); process.on('SIGINT', () => { log('INFO', 'shutdown', 'SIGINT'); server.close(() => process.exit(0)); }); process.on('uncaughtException', (e) => { log('ERROR', 'uncaught', e.message); }); process.on('unhandledRejection', (e) => { log('ERROR', 'unhandled', e.message); });