import express from 'express' import { users, userRoles, rolePermissions, permissions } from '../db/seedAuth.js' import { issueTokenPair, accessTokens, refreshTokens, genSmsCode, verifySmsCode, loginFailCounts, MAX_LOGIN_FAIL, REFRESH_TOKEN_TTL_MS } from '../state.js' import { sendOk, sendFail } from '../utils/response.js' import { requireAuth } from '../middleware/requireAuth.js' const router = express.Router() const COOKIE_OPTS = { httpOnly: true, sameSite: 'lax', maxAge: REFRESH_TOKEN_TTL_MS } function toLoginVo(user, accessToken, forceChangePwd) { return { token: accessToken, username: user.username, realName: user.realName, userId: user.id, phone: user.phone, userType: user.userType, organizationId: user.organizationId, forceChangePwd: !!forceChangePwd } } function toUserDetailVo(user) { return { id: user.id, username: user.username, phone: user.phone, realName: user.realName, organizationId: user.organizationId, status: user.status, email: user.email, lastLoginTime: user.lastLoginTime, userType: user.userType } } function issueAndRespond(req, res, user) { loginFailCounts.set(user.username, 0) user.lastLoginTime = new Date().toISOString().slice(0, 19).replace('T', ' ') const { accessToken, refreshToken } = issueTokenPair(user.id) res.cookie('refresh_token', refreshToken, COOKIE_OPTS) sendOk(res, toLoginVo(user, accessToken, user.forceChangePwd)) } // 发送短信验证码:真实生成6位随机码(打印到控制台)+ 固定万能码123456同时有效,5分钟过期 router.post('/send-sms-code', (req, res) => { const { phone, businessType } = req.body || {} if (!phone || !businessType) return sendFail(res, '手机号和业务类型不能为空', 400) genSmsCode(businessType, phone) sendOk(res, null, '验证码已发送') }) // 账号密码登录:连续失败5次自动锁定 router.post('/login-password', (req, res) => { const { username, password } = req.body || {} if (!username || !password) return sendFail(res, '用户名和密码不能为空', 400) const user = users.find((u) => u.username === username) if (!user) return sendFail(res, '用户名或密码错误', 401) if (user.status === 'LOCKED') return sendFail(res, '账号已锁定,请联系管理员', 403) if (user.password !== password) { const fails = (loginFailCounts.get(username) || 0) + 1 loginFailCounts.set(username, fails) if (fails >= MAX_LOGIN_FAIL) { user.status = 'LOCKED' return sendFail(res, '连续输错密码5次,账号已锁定', 403) } return sendFail(res, `用户名或密码错误,还可尝试${MAX_LOGIN_FAIL - fails}次`, 401) } issueAndRespond(req, res, user) }) // 手机验证码登录 router.post('/login-phone', (req, res) => { const { phone, smsCode } = req.body || {} if (!phone || !smsCode) return sendFail(res, '手机号和验证码不能为空', 400) const user = users.find((u) => u.phone === phone) if (!user) return sendFail(res, '该手机号未注册', 401) if (user.status === 'LOCKED') return sendFail(res, '账号已锁定,请联系管理员', 403) if (!verifySmsCode('LOGIN', phone, smsCode)) return sendFail(res, '验证码错误或已过期', 401) issueAndRespond(req, res, user) }) // 刷新访问令牌:从httpOnly Cookie读取refresh_token,无需登录即可访问 router.post('/refresh-token', (req, res) => { const refreshToken = req.cookies?.refresh_token const record = refreshToken ? refreshTokens.get(refreshToken) : null if (!record || record.expireAt < Date.now()) { return sendFail(res, 'refresh_token已失效,请重新登录', 401, null) } const user = users.find((u) => u.id === record.userId) if (!user) return sendFail(res, '用户不存在', 401) refreshTokens.delete(refreshToken) const { accessToken, refreshToken: newRefreshToken } = issueTokenPair(user.id) res.cookie('refresh_token', newRefreshToken, COOKIE_OPTS) sendOk(res, toLoginVo(user, accessToken, user.forceChangePwd)) }) // 登出 router.post('/logout', (req, res) => { const header = req.headers.authorization || '' const token = header.startsWith('Bearer ') ? header.slice(7) : '' if (token) accessTokens.delete(token) const refreshToken = req.cookies?.refresh_token if (refreshToken) refreshTokens.delete(refreshToken) res.clearCookie('refresh_token', COOKIE_OPTS) sendOk(res, null, '已退出登录') }) // 获取当前用户信息(基于JWT/access_token) router.post('/user-info', requireAuth, (req, res) => { const user = users.find((u) => u.id === req.userId) if (!user) return sendFail(res, '用户不存在', 404) sendOk(res, toUserDetailVo(user)) }) // 查询用户权限详情(扁平列表) router.get('/user/permission-detail', requireAuth, (req, res) => { const userId = Number(req.query.userId) const myRoleIds = userRoles.filter((ur) => ur.userId === userId).map((ur) => ur.roleId) const myPermissionIds = new Set( rolePermissions.filter((rp) => myRoleIds.includes(rp.roleId)).map((rp) => rp.permissionId) ) const flatList = permissions .filter((p) => myPermissionIds.has(p.id)) .map((p) => ({ id: p.id, permissionCode: p.permissionCode, permissionName: p.permissionName, permissionType: p.permissionType, parentId: p.parentId, level: p.level, sortOrder: p.sortOrder, icon: p.icon, routePath: p.routePath, pageResourcePathList: p.pageResourcePathList, apiResourcePathList: p.apiResourcePathList })) sendOk(res, flatList) }) // 修改密码:基于当前登录用户 router.post('/change-password', requireAuth, (req, res) => { const { oldPassword, newPassword } = req.body || {} if (!oldPassword || !newPassword) return sendFail(res, '旧密码和新密码不能为空', 400) const user = users.find((u) => u.id === req.userId) if (!user) return sendFail(res, '用户不存在', 404) if (user.password !== oldPassword) return sendFail(res, '旧密码不正确', 400) user.password = newPassword user.forceChangePwd = false sendOk(res, null, '密码修改成功') }) export default router