130 lines
3.2 KiB
JavaScript
130 lines
3.2 KiB
JavaScript
|
|
const express = require('express');
|
||
|
|
const bcrypt = require('bcryptjs');
|
||
|
|
const { generateToken, authMiddleware } = require('../middleware/auth');
|
||
|
|
const { getLockStatus, recordFailedAttempt, clearLock, getConfig } = require('../utils/loginLimiter');
|
||
|
|
|
||
|
|
const router = express.Router();
|
||
|
|
|
||
|
|
const users = [
|
||
|
|
{
|
||
|
|
id: '1',
|
||
|
|
username: 'admin',
|
||
|
|
password: bcrypt.hashSync('1221xian', 10)
|
||
|
|
}
|
||
|
|
];
|
||
|
|
|
||
|
|
router.post('/login', async (req, res) => {
|
||
|
|
const { username, password } = req.body;
|
||
|
|
|
||
|
|
if (!username || !password) {
|
||
|
|
return res.status(400).json({ error: '请输入用户名和密码' });
|
||
|
|
}
|
||
|
|
|
||
|
|
const lockStatus = getLockStatus(username);
|
||
|
|
|
||
|
|
if (lockStatus.locked) {
|
||
|
|
return res.status(429).json({
|
||
|
|
error: '登录已被临时限制',
|
||
|
|
locked: true,
|
||
|
|
remainingMs: lockStatus.remainingMs,
|
||
|
|
lockedUntil: lockStatus.lockedUntil
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
const user = users.find(u => u.username === username);
|
||
|
|
|
||
|
|
if (!user) {
|
||
|
|
const newLockStatus = recordFailedAttempt(username);
|
||
|
|
return res.status(401).json({
|
||
|
|
error: '用户名或密码错误',
|
||
|
|
attempts: newLockStatus.attempts,
|
||
|
|
maxAttempts: getConfig().maxAttempts
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
const isValidPassword = await bcrypt.compare(password, user.password);
|
||
|
|
|
||
|
|
if (!isValidPassword) {
|
||
|
|
const newLockStatus = recordFailedAttempt(username);
|
||
|
|
return res.status(401).json({
|
||
|
|
error: '用户名或密码错误',
|
||
|
|
attempts: newLockStatus.attempts,
|
||
|
|
maxAttempts: getConfig().maxAttempts,
|
||
|
|
locked: newLockStatus.locked,
|
||
|
|
remainingMs: newLockStatus.remainingMs,
|
||
|
|
lockedUntil: newLockStatus.lockedUntil
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
clearLock(username);
|
||
|
|
|
||
|
|
const token = generateToken(user.id);
|
||
|
|
|
||
|
|
res.json({
|
||
|
|
token,
|
||
|
|
user: {
|
||
|
|
id: user.id,
|
||
|
|
username: user.username
|
||
|
|
}
|
||
|
|
});
|
||
|
|
});
|
||
|
|
|
||
|
|
router.get('/lock-status/:username', (req, res) => {
|
||
|
|
const { username } = req.params;
|
||
|
|
|
||
|
|
if (!username) {
|
||
|
|
return res.status(400).json({ error: '用户名不能为空' });
|
||
|
|
}
|
||
|
|
|
||
|
|
const lockStatus = getLockStatus(username);
|
||
|
|
const config = getConfig();
|
||
|
|
|
||
|
|
res.json({
|
||
|
|
locked: lockStatus.locked,
|
||
|
|
attempts: lockStatus.attempts,
|
||
|
|
maxAttempts: config.maxAttempts,
|
||
|
|
remainingMs: lockStatus.remainingMs,
|
||
|
|
lockedUntil: lockStatus.lockedUntil
|
||
|
|
});
|
||
|
|
});
|
||
|
|
|
||
|
|
router.get('/verify', authMiddleware, (req, res) => {
|
||
|
|
const user = users.find(u => u.id === req.userId);
|
||
|
|
res.json({
|
||
|
|
user: {
|
||
|
|
id: user.id,
|
||
|
|
username: user.username
|
||
|
|
}
|
||
|
|
});
|
||
|
|
});
|
||
|
|
|
||
|
|
router.post('/change-password', authMiddleware, async (req, res) => {
|
||
|
|
const { currentPassword, newPassword } = req.body;
|
||
|
|
|
||
|
|
if (!currentPassword || !newPassword) {
|
||
|
|
return res.status(400).json({ error: '当前密码和新密码不能为空' });
|
||
|
|
}
|
||
|
|
|
||
|
|
if (newPassword.length < 6) {
|
||
|
|
return res.status(400).json({ error: '新密码长度至少6位' });
|
||
|
|
}
|
||
|
|
|
||
|
|
const user = users.find(u => u.id === req.userId);
|
||
|
|
|
||
|
|
if (!user) {
|
||
|
|
return res.status(404).json({ error: '用户不存在' });
|
||
|
|
}
|
||
|
|
|
||
|
|
const isValidPassword = await bcrypt.compare(currentPassword, user.password);
|
||
|
|
|
||
|
|
if (!isValidPassword) {
|
||
|
|
return res.status(401).json({ error: '当前密码错误' });
|
||
|
|
}
|
||
|
|
|
||
|
|
user.password = bcrypt.hashSync(newPassword, 10);
|
||
|
|
|
||
|
|
res.json({ message: '密码修改成功' });
|
||
|
|
});
|
||
|
|
|
||
|
|
module.exports = router;
|