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 ⊢ 心跳不停 · 闭环继续 · 安全升级完成
149 lines
5.4 KiB
JavaScript
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)`);
|
|
});
|