const express = require('express'); const nodemailer = require('nodemailer'); const crypto = require('crypto'); const bcrypt = require('bcryptjs'); const rateLimit = require('express-rate-limit'); const db = require('../db'); const { authMiddleware, adminOnly } = require('../middleware/auth'); const router = express.Router(); // M2:验证码重发/校验限流——15 分钟窗口内每 IP 最多 10 次(防 8 位验证码爆破 + 邮箱轰炸) const verifyLimiter = rateLimit({ windowMs: 15 * 60 * 1000, max: 10, standardHeaders: true, legacyHeaders: false, message: { error: '操作过于频繁,请 15 分钟后再试' }, }); function getTransporter() { const host = db.getSetting('smtp_host'); if (!host) return null; return nodemailer.createTransport({ host, port: parseInt(db.getSetting('smtp_port')) || 587, secure: parseInt(db.getSetting('smtp_port')) === 465, auth: { user: db.getSetting('smtp_user'), pass: db.getSetting('smtp_pass') }, // L4:保留 rejectUnauthorized: false(不改行为)——内网 SMTP 多为自签证书,改 true 会大面积失败。 // 风险:SMTP 凭据在 TLS 握手时可能被中间人嗅探。生产环境建议改 true 并将自签证书加入系统 CA。 tls: { rejectUnauthorized: false }, }); } function emailTemplate(title, body) { const siteName = db.getSetting('site_name') || 'RainWeb'; const color = db.getSetting('primary_color') || '#6750a4'; return `
如果您收到此邮件,说明 SMTP 配置正确。
'), }); res.json({ message: '测试邮件已发送至 ' + to }); } catch (e) { console.error('邮件发送失败:', e.message); res.status(500).json({ error: '邮件发送失败' }); } }); // Re-send verification code router.post('/send-verify', verifyLimiter, async (req, res) => { const { email, username } = req.body; if (!email || !username) return res.status(400).json({ error: '参数不完整' }); // 先取出旧 pending 记录(其中存有注册时加密的密码),避免删除后无法重建; // M2:只接受 15 分钟内的有效记录(过期视为已失效并拒绝,防旧码无限重发) const pending = db.get( `SELECT * FROM pending_users WHERE email = ? AND created_at > datetime('now','-15 minutes')`, [email]); if (!pending) return res.status(400).json({ error: '未找到待验证的注册信息,请重新注册' }); // 先确认 SMTP 已配置,避免删除旧记录后无法发送新验证码 const transporter = getTransporter(); if (!transporter) return res.status(400).json({ error: 'SMTP 未配置,请联系管理员' }); // 生成新的验证码,删除旧记录后重新插入新的 pending 记录 const code = crypto.randomInt(10000000, 100000000).toString(); db.run('DELETE FROM pending_users WHERE email = ?', [email]); db.run('INSERT INTO pending_users (username, password, email, token) VALUES (?, ?, ?, ?)', [pending.username, pending.password, email, code]); try { const siteName = db.getSetting('site_name') || 'RainWeb'; const color = db.getSetting('primary_color') || '#6750a4'; await transporter.sendMail({ from: `"${db.getSetting('smtp_from_name')}" <${db.getSetting('smtp_from_email')}>`, to: email, subject: '验证邮箱 - ' + siteName, html: `您好 ${username},
您的邮箱验证码为:
验证码有效期为 10 分钟。