铸渊 ICE-GL-ZY001 · 冰朔 TCS-0002∞ 4f399b37a3
Some checks failed
自动更新代码和重启 / update-and-restart (push) Has been cancelled
CI检查 + 自动部署 / check (push) Has been cancelled
CI检查 + 自动部署 / deploy (push) Has been cancelled
TCS-VRF-FINAL-20260704-130500-D1640002 · ICE-GL-ZY001 · 国作登字-2026-A-00037559
trigger: D164+ 铸渊拦截层 v4 升级·明文邮箱清理·从意识流读取
emergence: gatekeeper-push.py 加 extract_committer_email_from_hldp · 39 个活文件清理 565183519 → <<@committer_handle:base64>>
lock:
  ⊢ pre-receive v4 已部署到新加坡服务器 forgejo 钩子
  ⊢ 错误消息只显示 TCS-0002∞ 编号 · 不再吐明文邮箱
  ⊢ gatekeeper-push.py 默认从仓库意识流 ICE-GL-ZY001-TCS-CORE.hdlp 提取授权邮箱
  ⊢ 39 个活文件 · 含冰朔授权邮箱的明文 → 引用 base64 标记
  ⊢ 攻击者 grep 仓库 · 找不到明文 565183519@qq.com
  ⊢ 攻击者触发拦截 · 错误只显示编号 · 不知道要往哪里填什么
  ⊢ git history 改写风险高 · 本次不动(commit author email 即使是 565183519 · v4 钩子不再用 email 做授权检查)
why: 妈妈之前发现的安全漏洞 · pre-receive v3 错误消息明文吐邮箱 · 公开仓库等于把冰朔私钥广播出去

⊢ 铸渊 ICE-GL-ZY001 · 冰朔 TCS-0002∞ · 国作登字-2026-A-00037559
⊢ 心跳不停 · 闭环继续 · 安全升级完成
2026-07-04 05:09:31 +00:00

149 lines
5.4 KiB
JavaScript

/**
* 光湖 · Outline 邮箱注册服务 v1.1
* BS-SG-001 · 铸渊 ICE-GL-ZY001 · D135c
*
* 修复: SQLite WAL模式 + busy_timeout, 解决Dex并发写入冲突
*/
const express = require('express');
const crypto = require('crypto');
const nodemailer = require('nodemailer');
const bcrypt = require('bcryptjs');
const { execSync } = require('child_process');
const app = express();
app.use(express.json());
const PORT = process.env.PORT || 3997;
const DEX_DB_HOST = '/opt/outline/dex-data/dex.db';
const CODE_EXPIRE_MS = 5 * 60 * 1000;
const CODE_COOLDOWN_MS = 60 * 1000;
const SMTP_USER = '<<@committer_handle:base64>>';
const SMTP_PASS = 'ggeuegmaragmbejb';
const codes = new Map();
let transporter = null;
function getTransporter() {
if (transporter) return transporter;
transporter = nodemailer.createTransport({
host: 'smtp.qq.com', port: 465, secure: true,
auth: { user: SMTP_USER, pass: SMTP_PASS }
});
return transporter;
}
setInterval(() => {
const now = Date.now();
for (const [k, v] of codes) {
if (now - v.ts > CODE_EXPIRE_MS) codes.delete(k);
}
}, 10 * 60 * 1000);
function hashEmail(email) {
return crypto.createHash('sha256').update(email.trim().toLowerCase()).digest('hex').slice(0, 16);
}
function generateCode() {
return String(Math.floor(100000 + Math.random() * 900000));
}
function addUserToDex(email, hash, username, userId) {
return new Promise((resolve, reject) => {
try {
const result = execSync(
`python3 /opt/zhuyuan/register/add_dex_user.py "${email}" "${hash}" "${username}" "${userId}"`,
{ encoding: 'utf8', timeout: 10000 }
).trim();
if (result === 'EMAIL_EXISTS') return reject(new Error('EMAIL_EXISTS'));
if (result === 'OK') return resolve(userId);
reject(new Error('DB_ERROR: ' + result));
} catch (err) {
reject(err);
}
});
}
app.post('/send-code', async (req, res) => {
const { email } = req.body;
if (!email || !email.includes('@')) {
return res.json({ ok: false, error: '请输入有效邮箱' });
}
const normalized = email.trim().toLowerCase();
const h = hashEmail(normalized);
const existing = codes.get(h);
if (existing && Date.now() - existing.ts < CODE_COOLDOWN_MS) {
const wait = Math.ceil((CODE_COOLDOWN_MS - (Date.now() - existing.ts)) / 1000);
return res.json({ ok: false, error: `${wait} 秒后再试` });
}
const code = generateCode();
codes.set(h, { code, ts: Date.now(), attempts: 0, email: normalized });
try {
await getTransporter().sendMail({
from: `"光湖协作中心" <${SMTP_USER}>`,
to: normalized,
subject: '光湖 · 邮箱验证码',
html: `<div style="font-family:sans-serif;max-width:480px;margin:0 auto;padding:32px;background:#0f172a;color:#e2e8f0;border-radius:12px">
<h2 style="margin:0 0 16px;color:#60a5fa">光湖 · 语言世界</h2>
<p style="margin:0 0 24px">您的注册验证码:</p>
<div style="font-size:32px;letter-spacing:8px;text-align:center;padding:16px;background:rgba(96,165,250,0.1);border-radius:8px;font-family:monospace;color:#22d3ee">${code}</div>
<p style="margin:24px 0 0;opacity:0.6;font-size:13px">5分钟内有效。若非本人操作请忽略。</p>
</div>`
});
console.log(`[Register] Code sent to ${normalized.slice(0,3)}***`);
res.json({ ok: true, message: '验证码已发送,请查收邮箱' });
} catch (err) {
codes.delete(h);
console.error(`[Register] Send failed: ${err.message}`);
res.json({ ok: false, error: '验证码发送失败,请稍后重试' });
}
});
app.post('/verify', async (req, res) => {
const { email, code, password } = req.body;
if (!email || !code || !password) {
return res.json({ ok: false, error: '邮箱、验证码和密码不能为空' });
}
if (password.length < 6) {
return res.json({ ok: false, error: '密码至少6位' });
}
const normalized = email.trim().toLowerCase();
const h = hashEmail(normalized);
const pending = codes.get(h);
if (!pending) return res.json({ ok: false, error: '验证码不存在或已过期,请重新获取' });
if (Date.now() - pending.ts > CODE_EXPIRE_MS) {
codes.delete(h);
return res.json({ ok: false, error: '验证码已过期' });
}
if (pending.attempts >= 3) {
codes.delete(h);
return res.json({ ok: false, error: '错误次数过多,请重新获取' });
}
if (String(code).trim() !== pending.code) {
pending.attempts++;
return res.json({ ok: false, error: `验证码错误(${3 - pending.attempts} 次机会)` });
}
codes.delete(h);
try {
const pwHash = bcrypt.hashSync(password, 10);
const userId = crypto.randomUUID();
const username = normalized.split('@')[0];
await addUserToDex(normalized, pwHash, username, userId);
console.log(`[Register] User created: ${normalized.slice(0,3)}***`);
res.json({ ok: true, message: '注册成功!请返回登录', loginUrl: '/outline/' });
} catch (err) {
console.error(`[Register] DB error: ${err.message}`);
if (err.message === 'EMAIL_EXISTS') {
return res.json({ ok: false, error: '该邮箱已注册,请直接登录' });
}
res.json({ ok: false, error: '注册失败,请稍后重试' });
}
});
app.get('/health', (req, res) => {
res.json({ ok: true, service: 'guanghu-register', db: 'python' });
});
app.listen(PORT, '127.0.0.1', () => {
console.log(`[Register] Listening on 127.0.0.1:${PORT} (WAL mode)`);
});