diff --git a/backend/server.js b/backend/server.js index d4041d7..800303d 100644 --- a/backend/server.js +++ b/backend/server.js @@ -1100,6 +1100,42 @@ function checkMailRateLimit(req, type = 'mail') { } } +// ===== 验证码验证辅助函数 ===== + +/** + * 验证验证码 + * @param {Object} req - 请求对象 + * @param {string} captcha - 用户输入的验证码 + * @returns {{valid: boolean, message?: string}} 验证结果 + */ +function verifyCaptcha(req, captcha) { + if (!captcha) { + return { valid: false, message: '请输入验证码' }; + } + + const sessionCaptcha = req.session.captcha; + const captchaTime = req.session.captchaTime; + + if (!sessionCaptcha || !captchaTime) { + return { valid: false, message: '验证码已过期,请刷新验证码' }; + } + + // 验证码有效期5分钟 + if (Date.now() - captchaTime > 5 * 60 * 1000) { + return { valid: false, message: '验证码已过期,请刷新验证码' }; + } + + if (captcha.toLowerCase() !== sessionCaptcha) { + return { valid: false, message: '验证码错误' }; + } + + // 验证通过后清除session中的验证码 + delete req.session.captcha; + delete req.session.captchaTime; + + return { valid: true }; +} + // ===== 公开API ===== // 健康检查 @@ -1164,7 +1200,8 @@ app.post('/api/register', .isLength({ min: 3, max: 20 }).withMessage('用户名长度3-20个字符') .matches(USERNAME_REGEX).withMessage('用户名仅允许中英文、数字、下划线、点和短横线'), body('email').isEmail().withMessage('邮箱格式不正确'), - body('password').isLength({ min: 6 }).withMessage('密码至少6个字符') + body('password').isLength({ min: 6 }).withMessage('密码至少6个字符'), + body('captcha').notEmpty().withMessage('请输入验证码') ], async (req, res) => { const errors = validationResult(req); @@ -1176,6 +1213,16 @@ app.post('/api/register', } try { + // 验证验证码 + const { captcha } = req.body; + const captchaResult = verifyCaptcha(req, captcha); + if (!captchaResult.valid) { + return res.status(400).json({ + success: false, + message: captchaResult.message + }); + } + checkMailRateLimit(req, 'verify'); const { username, email, password } = req.body; @@ -1263,7 +1310,8 @@ app.post('/api/resend-verification', [ body('username') .optional({ checkFalsy: true }) .isLength({ min: 3 }).withMessage('用户名格式不正确') - .matches(USERNAME_REGEX).withMessage('用户名仅允许中英文、数字、下划线、点和短横线') + .matches(USERNAME_REGEX).withMessage('用户名仅允许中英文、数字、下划线、点和短横线'), + body('captcha').notEmpty().withMessage('请输入验证码') ], async (req, res) => { const errors = validationResult(req); if (!errors.isEmpty()) { @@ -1271,6 +1319,16 @@ app.post('/api/resend-verification', [ } try { + // 验证验证码 + const { captcha } = req.body; + const captchaResult = verifyCaptcha(req, captcha); + if (!captchaResult.valid) { + return res.status(400).json({ + success: false, + message: captchaResult.message + }); + } + checkMailRateLimit(req, 'verify'); const { email, username } = req.body; @@ -1332,15 +1390,25 @@ app.get('/api/verify-email', async (req, res) => { // 发起密码重置(邮件) app.post('/api/password/forgot', [ - body('email').isEmail().withMessage('邮箱格式不正确') + body('email').isEmail().withMessage('邮箱格式不正确'), + body('captcha').notEmpty().withMessage('请输入验证码') ], async (req, res) => { const errors = validationResult(req); if (!errors.isEmpty()) { return res.status(400).json({ success: false, errors: errors.array() }); } - const { email } = req.body; + const { email, captcha } = req.body; try { + // 验证验证码 + const captchaResult = verifyCaptcha(req, captcha); + if (!captchaResult.valid) { + return res.status(400).json({ + success: false, + message: captchaResult.message + }); + } + checkMailRateLimit(req, 'pwd_forgot'); const smtpConfig = getSmtpConfig(); diff --git a/frontend/app.html b/frontend/app.html index 78fbc48..7410130 100644 --- a/frontend/app.html +++ b/frontend/app.html @@ -1038,7 +1038,14 @@ 点击图片刷新验证码
- 邮箱未验证?点击重发激活邮件 +
邮箱未验证?请输入验证码后重发激活邮件
+
+ + + +
@@ -1049,7 +1056,7 @@ 登录 -
+
@@ -1062,6 +1069,13 @@
+
+ +
+ + +
+
@@ -2590,7 +2604,7 @@