98 lines
3.7 KiB
JavaScript
98 lines
3.7 KiB
JavaScript
import express from 'express'
|
|
import crypto from 'crypto'
|
|
import { users } from '../data/users.js'
|
|
import { menuTree, flattenButtons } from '../data/menu.js'
|
|
import {
|
|
accessTokens,
|
|
refreshTokens,
|
|
ACCESS_TOKEN_TTL_MS,
|
|
REFRESH_TOKEN_TTL_MS,
|
|
FIXED_SMS_CODE
|
|
} from '../state.js'
|
|
import { requireAuth } from '../middleware/requireAuth.js'
|
|
|
|
const router = express.Router()
|
|
|
|
function findUser(username) {
|
|
return users.find((u) => u.username === username)
|
|
}
|
|
|
|
router.post('/sms/send', (req, res) => {
|
|
const { username, password } = req.body || {}
|
|
const user = findUser(username)
|
|
if (!user) return res.json({ code: 40001, msg: '账号或密码错误', data: null })
|
|
if (user.status === 'locked') return res.json({ code: 40002, msg: '账号已锁定,请联系管理员', data: null })
|
|
if (user.password !== password) return res.json({ code: 40001, msg: '账号或密码错误', data: null })
|
|
console.log(`[mock-server] 验证码已发送至 ${user.phone}: ${FIXED_SMS_CODE}`)
|
|
res.json({ code: 200, msg: 'ok', data: null })
|
|
})
|
|
|
|
router.post('/login', (req, res) => {
|
|
const { username, password, code } = req.body || {}
|
|
const user = findUser(username)
|
|
if (!user) return res.json({ code: 40001, msg: '账号或密码错误', data: null })
|
|
if (user.status === 'locked') return res.json({ code: 40002, msg: '账号已锁定,请联系管理员', data: null })
|
|
if (user.password !== password) {
|
|
user.failCount += 1
|
|
if (user.failCount >= 5) {
|
|
user.status = 'locked'
|
|
return res.json({ code: 40002, msg: '连续输错5次,账号已锁定', data: null })
|
|
}
|
|
return res.json({ code: 40001, msg: '账号或密码错误', data: null })
|
|
}
|
|
if (code !== FIXED_SMS_CODE) return res.json({ code: 40003, msg: '验证码错误或已过期', data: null })
|
|
user.failCount = 0
|
|
user.lastLoginAt = new Date().toISOString().slice(0, 19).replace('T', ' ')
|
|
|
|
const accessToken = crypto.randomUUID()
|
|
const refreshToken = crypto.randomUUID()
|
|
accessTokens.set(accessToken, { userId: user.id, expireAt: Date.now() + ACCESS_TOKEN_TTL_MS })
|
|
refreshTokens.set(refreshToken, { userId: user.id, expireAt: Date.now() + REFRESH_TOKEN_TTL_MS })
|
|
|
|
res.cookie('refresh_token', refreshToken, {
|
|
httpOnly: true,
|
|
maxAge: REFRESH_TOKEN_TTL_MS,
|
|
sameSite: 'lax'
|
|
})
|
|
res.json({
|
|
code: 200,
|
|
msg: 'ok',
|
|
data: {
|
|
accessToken,
|
|
userInfo: { id: user.id, username: user.username, realName: user.realName, orgId: user.orgId, roleIds: user.roleIds },
|
|
menuTree,
|
|
buttonCodes: flattenButtons(menuTree),
|
|
mustChangePassword: !!user.mustChangePassword
|
|
}
|
|
})
|
|
})
|
|
|
|
router.post('/refresh', (req, res) => {
|
|
const refreshToken = req.cookies?.refresh_token
|
|
const record = refreshToken && refreshTokens.get(refreshToken)
|
|
if (!record || record.expireAt < Date.now()) {
|
|
return res.status(401).json({ code: 40100, msg: '登录已过期,请重新登录', data: null })
|
|
}
|
|
const accessToken = crypto.randomUUID()
|
|
accessTokens.set(accessToken, { userId: record.userId, expireAt: Date.now() + ACCESS_TOKEN_TTL_MS })
|
|
res.json({ code: 200, msg: 'ok', data: { accessToken } })
|
|
})
|
|
|
|
router.post('/logout', (req, res) => {
|
|
const refreshToken = req.cookies?.refresh_token
|
|
if (refreshToken) refreshTokens.delete(refreshToken)
|
|
res.clearCookie('refresh_token')
|
|
res.json({ code: 200, msg: 'ok', data: null })
|
|
})
|
|
|
|
router.get('/userinfo', requireAuth, (req, res) => {
|
|
const user = users.find((u) => u.id === req.userId)
|
|
res.json({ code: 200, msg: 'ok', data: { id: user.id, username: user.username, realName: user.realName, orgId: user.orgId, roleIds: user.roleIds } })
|
|
})
|
|
|
|
router.get('/menu', requireAuth, (req, res) => {
|
|
res.json({ code: 200, msg: 'ok', data: { menuTree, buttonCodes: flattenButtons(menuTree) } })
|
|
})
|
|
|
|
export default router
|