const express = require('express'); const jwt = require('jsonwebtoken'); const bcrypt = require('bcryptjs'); const db = require('../db'); const config = require('../config'); const logger = require('../utils/logger'); const router = express.Router(); const loginAttempts = new Map(); const LOGIN_WINDOW_MS = 15 * 60 * 1000; const LOGIN_MAX_FAILURES = 5; function normalizeLoginKey(req, username) { return `${req.ip || req.socket.remoteAddress || 'unknown'}:${String(username || '').trim().toLowerCase()}`; } function getLoginAttempt(key) { const now = Date.now(); const attempt = loginAttempts.get(key); if (!attempt || attempt.resetAt <= now) { const fresh = { failures: 0, resetAt: now + LOGIN_WINDOW_MS }; loginAttempts.set(key, fresh); return fresh; } return attempt; } function clearExpiredLoginAttempts() { const now = Date.now(); for (const [key, attempt] of loginAttempts) { if (attempt.resetAt <= now) loginAttempts.delete(key); } } function recordFailedLogin(key) { const attempt = getLoginAttempt(key); attempt.failures += 1; if (loginAttempts.size > 1000) clearExpiredLoginAttempts(); return attempt; } function resetLoginAttempts(key) { loginAttempts.delete(key); } // JWT 中间件 function auth(req, res, next) { const token = req.headers.authorization?.split(' ')[1]; if (!token) return res.status(401).json({ error: '未登录' }); try { req.user = jwt.verify(token, config.jwtSecret); next(); } catch { res.status(401).json({ error: '登录已过期' }); } } // 登录 router.post('/login', (req, res) => { const { username, password } = req.body; if (!username || !password) { return res.status(400).json({ error: '用户名和密码不能为空' }); } const loginKey = normalizeLoginKey(req, username); const attempt = getLoginAttempt(loginKey); if (attempt.failures >= LOGIN_MAX_FAILURES) { const retryAfter = Math.ceil((attempt.resetAt - Date.now()) / 1000); res.setHeader('Retry-After', String(Math.max(retryAfter, 1))); return res.status(429).json({ error: '登录失败次数过多,请稍后再试' }); } const user = db.prepare('SELECT * FROM users WHERE username = ?').get(username); if (!user || !bcrypt.compareSync(password, user.password)) { recordFailedLogin(loginKey); logger.warn(`登录失败: ${username}`); return res.status(401).json({ error: '用户名或密码错误' }); } resetLoginAttempts(loginKey); const token = jwt.sign({ id: user.id, username: user.username }, config.jwtSecret, { expiresIn: config.jwtExpiresIn }); logger.info(`用户登录: ${username}`); res.json({ token, username: user.username }); }); // 注册 router.post('/register', (req, res) => { if (!config.enableRegistration) { return res.status(403).json({ error: '注册已关闭' }); } const { username, password } = req.body; if (!username || !password) { return res.status(400).json({ error: '用户名和密码不能为空' }); } if (username.length < 2 || username.length > 20) { return res.status(400).json({ error: '用户名长度需在2-20个字符之间' }); } if (password.length < 6) { return res.status(400).json({ error: '密码长度不能少于6个字符' }); } const existing = db.prepare('SELECT id FROM users WHERE username = ?').get(username); if (existing) { return res.status(409).json({ error: '用户名已存在' }); } const hash = bcrypt.hashSync(password, 10); const result = db.prepare('INSERT INTO users (username, password) VALUES (?, ?)').run(username, hash); logger.info(`新用户注册: ${username}`); const token = jwt.sign({ id: result.lastInsertRowid, username }, config.jwtSecret, { expiresIn: config.jwtExpiresIn }); res.json({ token, username }); }); module.exports = { router, auth };