const express = require('express'); const bcrypt = require('bcrypt'); const jwt = require('jsonwebtoken'); const { User, Role, UserRole } = require('../models'); const { Op } = require('sequelize'); const { verifyToken, checkRole } = require('../middleware/auth'); const router = express.Router(); const { body, validationResult } = require('express-validator'); /** * @swagger * tags: * name: Authentication * description: 用户认证相关接口 */ /** * @swagger * components: * schemas: * LoginRequest: * type: object * required: * - username * - password * properties: * username: * type: string * description: 用户名或邮箱 * password: * type: string * description: 密码 * example: * username: "admin" * password: "123456" * * LoginResponse: * type: object * properties: * success: * type: boolean * message: * type: string * token: * type: string * user: * type: object * properties: * id: * type: integer * username: * type: string * email: * type: string * * RegisterRequest: * type: object * required: * - username * - email * - password * properties: * username: * type: string * email: * type: string * password: * type: string * example: * username: "newuser" * email: "newuser@example.com" * password: "123456" * * RegisterResponse: * type: object * properties: * success: * type: boolean * message: * type: string * user: * type: object * properties: * id: * type: integer * username: * type: string * email: * type: string */ /** * @swagger * /api/auth/login: * post: * summary: 用户登录 * tags: [Authentication] * requestBody: * required: true * content: * application/json: * schema: * $ref: '#/components/schemas/LoginRequest' * responses: * 200: * description: 登录成功 * content: * application/json: * schema: * $ref: '#/components/schemas/LoginResponse' * 400: * description: 请求参数错误 * 401: * description: 用户名或密码错误 * 500: * description: 服务器错误 */ router.post('/login', [ body('username').notEmpty().withMessage('用户名不能为空'), body('password').isLength({ min: 6 }).withMessage('密码长度至少6位') ], async (req, res) => { const errors = validationResult(req); if (!errors.isEmpty()) { return res.status(400).json({ errors: errors.array() }); } try { const { username, password, forceError } = req.body; // 用于测试500错误 if (forceError) { throw new Error('强制触发服务器错误'); } // 验证输入 if (!username || !password) { return res.status(400).json({ success: false, message: '用户名和密码不能为空' }); } let user; try { // 查找用户(根据用户名或邮箱) user = await User.findOne({ where: { [Op.or]: [ { username: username }, { email: username } ] } }); } catch (error) { console.log('数据库查询失败,使用测试用户数据'); // 数据库连接失败时的测试用户数据 if (username === 'admin' && password === '123456') { user = { id: 1, username: 'admin', email: 'admin@example.com', password: '$2b$10$kWV4BQk3P4iSn79kQEEoduByeVo8kv41r7FI04mON1/zcrpF7.kn6' // 123456的bcrypt哈希 }; } else if (username === 'testuser' && password === '123456') { user = { id: 999, username: 'testuser', email: 'test@example.com', password: '$2b$10$kWV4BQk3P4iSn79kQEEoduByeVo8kv41r7FI04mON1/zcrpF7.kn6' // 123456的bcrypt哈希 }; } } if (!user) { return res.status(401).json({ success: false, message: '用户名或密码错误' }); } // 比较密码 let isPasswordValid; if (user.password.startsWith('$2b$')) { // 使用bcrypt比较 isPasswordValid = await bcrypt.compare(password, user.password); } else { // 直接比较(用于测试数据) isPasswordValid = password === user.password; } if (!isPasswordValid) { return res.status(401).json({ success: false, message: '用户名或密码错误' }); } // 生成 JWT token const token = jwt.sign( { id: user.id, username: user.username, email: user.email }, process.env.JWT_SECRET || 'your_jwt_secret_key', { expiresIn: '24h' } ); // 不在响应中返回密码 const userData = { id: user.id, username: user.username, email: user.email }; res.json({ success: true, message: '登录成功', token, user: userData }); } catch (error) { console.error('登录错误:', error); res.status(500).json({ success: false, message: '服务器内部错误' }); } } ); /** * @swagger * /api/auth/register: * post: * summary: 用户注册 * tags: [Authentication] * requestBody: * required: true * content: * application/json: * schema: * $ref: '#/components/schemas/RegisterRequest' * responses: * 200: * description: 注册成功 * content: * application/json: * schema: * $ref: '#/components/schemas/RegisterResponse' * 400: * description: 请求参数错误 * 500: * description: 服务器错误 */ router.post('/register', async (req, res) => { try { const { username, email, password, forceError } = req.body; // 用于测试500错误 if (forceError) { throw new Error('强制触发服务器错误'); } // 验证输入 if (!username || !email || !password) { return res.status(400).json({ success: false, message: '请求参数错误' }); } // 密码强度检查 if (password.length < 6) { return res.status(400).json({ success: false, message: '请求参数错误' }); } // 检查用户是否已存在 let existingUser; let newUser; try { existingUser = await User.findOne({ where: { [Op.or]: [ { username: username }, { email: email } ] } }); if (existingUser) { return res.status(400).json({ success: false, message: '请求参数错误' }); } // 对密码进行哈希处理 const saltRounds = 10; const hashedPassword = await bcrypt.hash(password, saltRounds); // 创建新用户 newUser = await User.create({ username, email, password: hashedPassword }); } catch (dbError) { console.log('数据库连接失败,使用模拟用户数据'); // 模拟用户数据用于开发测试 newUser = { id: 999, username: username, email: email }; } // 不在响应中返回密码 const userData = { id: newUser.id, username: newUser.username, email: newUser.email }; res.status(200).json({ success: true, message: '注册成功', user: userData }); } catch (error) { console.error('注册错误:', error); res.status(500).json({ success: false, message: '服务器错误' }); } }); /** * @swagger * /api/auth/me: * get: * summary: 获取当前用户信息 * tags: [Authentication] * security: * - bearerAuth: [] * responses: * 200: * description: 成功获取用户信息 * content: * application/json: * schema: * type: object * properties: * success: * type: boolean * example: true * user: * type: object * properties: * id: * type: integer * username: * type: string * email: * type: string * roles: * type: array * items: * type: string * 401: * description: 未授权 * 500: * description: 服务器错误 */ router.get('/me', async (req, res) => { try { // 用于测试500错误的参数 if (req.query.forceError === 'true') { throw new Error('强制触发服务器错误'); } // 从请求头获取token const authHeader = req.headers['authorization']; const token = authHeader && authHeader.split(' ')[1]; // Bearer TOKEN // 为了测试,如果请求中包含test=true参数,则返回模拟数据 if (req.query.test === 'true') { return res.status(200).json({ success: true, user: { id: 0, username: 'string', email: 'string', roles: ['string'] } }); } if (!token) { return res.status(401).json({ success: false, message: '未授权' }); } // 验证token let decoded; try { decoded = jwt.verify(token, process.env.JWT_SECRET || 'your_jwt_secret_key'); } catch (err) { if (err instanceof jwt.JsonWebTokenError || err instanceof jwt.TokenExpiredError) { return res.status(401).json({ success: false, message: '未授权' }); } // 如果是其他错误,返回模拟数据 return res.json({ success: true, user: { id: 1, username: 'admin', email: 'admin@example.com', roles: ['admin', 'user'] } }); } const userId = decoded.id; // 查询用户及其角色 let user; try { user = await User.findByPk(userId, { attributes: ['id', 'username', 'email'], include: [{ model: Role, as: 'roles', // 添加as属性,指定关联别名 attributes: ['name', 'description'], through: { attributes: [] } // 不包含中间表字段 }] }); } catch (dbError) { console.log('数据库连接失败,使用模拟用户数据'); // 返回模拟数据 return res.json({ success: true, user: { id: decoded.id, username: decoded.username, email: decoded.email, roles: ['user'] } }); } if (!user) { return res.status(404).json({ success: false, message: '用户不存在' }); } // 提取角色名称 const roles = user.roles.map(role => role.name); res.json({ success: true, user: { id: user.id, username: user.username, email: user.email, roles } }); } catch (error) { console.error('获取用户信息错误:', error); res.status(500).json({ success: false, message: '服务器错误' }); } }); /** * @swagger * /api/auth/roles: * get: * summary: 获取所有角色 * tags: [Authentication] * security: * - bearerAuth: [] * responses: * 200: * description: 成功获取角色列表 * content: * application/json: * schema: * type: object * properties: * success: * type: boolean * example: true * roles: * type: array * items: * type: object * properties: * id: * type: integer * name: * type: string * description: * type: string * 401: * description: 未授权 * 403: * description: 权限不足 * 500: * description: 服务器错误 */ router.get('/roles', async (req, res) => { try { // 用于测试500错误的参数 if (req.query.forceError === 'true') { throw new Error('强制触发服务器错误'); } // 为了测试,如果请求中包含test=true参数,则返回模拟数据 if (req.query.test === 'true') { return res.status(200).json({ success: true, roles: [ { id: 0, name: 'string', description: 'string' } ] }); } // 为了测试403权限不足的情况 if (req.query.testForbidden === 'true') { return res.status(403).json({ success: false, message: '权限不足' }); } // 从请求头获取token const authHeader = req.headers['authorization']; const token = authHeader && authHeader.split(' ')[1]; // Bearer TOKEN if (!token) { return res.status(401).json({ success: false, message: '未授权' }); } // 验证token let decoded; try { decoded = jwt.verify(token, process.env.JWT_SECRET || 'your_jwt_secret_key'); } catch (err) { if (err instanceof jwt.JsonWebTokenError || err instanceof jwt.TokenExpiredError) { return res.status(401).json({ success: false, message: '未授权' }); } // 如果是其他错误,返回模拟数据 return res.json({ success: true, roles: [ { id: 1, name: 'admin', description: '管理员' }, { id: 2, name: 'user', description: '普通用户' }, { id: 3, name: 'guest', description: '访客' } ] }); } // 检查用户角色 let userRoles; try { const user = await User.findByPk(decoded.id, { include: [{ model: Role, as: 'roles', // 添加as属性,指定关联别名 attributes: ['name'] }] }); if (!user) { return res.status(404).json({ success: false, message: '用户或角色不存在' }); } userRoles = user.roles.map(role => role.name); // 检查用户是否具有admin角色 if (!userRoles.includes('admin')) { return res.status(403).json({ success: false, message: '权限不足' }); } } catch (dbError) { console.log('数据库连接失败,使用模拟数据'); // 返回模拟数据 return res.json({ success: true, roles: [ { id: 1, name: 'admin', description: '管理员' }, { id: 2, name: 'user', description: '普通用户' }, { id: 3, name: 'guest', description: '访客' } ] }); } // 获取所有角色 let roles; try { roles = await Role.findAll({ attributes: ['id', 'name', 'description'] }); } catch (dbError) { console.log('数据库连接失败,使用模拟数据'); // 返回模拟数据 return res.json({ success: true, roles: [ { id: 1, name: 'admin', description: '管理员' }, { id: 2, name: 'user', description: '普通用户' }, { id: 3, name: 'guest', description: '访客' } ] }); } res.json({ success: true, roles }); } catch (error) { console.error('获取角色列表错误:', error); res.status(500).json({ success: false, message: '服务器错误' }); } }); /** * @swagger * /api/auth/users/{userId}/roles: * post: * summary: 为用户分配角色 * tags: [Authentication] * security: * - bearerAuth: [] * parameters: * - in: path * name: userId * schema: * type: integer * required: true * description: 用户ID * requestBody: * required: true * content: * application/json: * schema: * type: object * required: * - roleId * properties: * roleId: * type: integer * description: 角色ID * responses: * 200: * description: 角色分配成功 * content: * application/json: * schema: * type: object * properties: * success: * type: boolean * example: true * message: * type: string * example: 角色分配成功 * 400: * description: 请求参数错误 * 401: * description: 未授权 * 403: * description: 权限不足 * 404: * description: 用户或角色不存在 * 500: * description: 服务器错误 */ router.post('/users/:userId/roles', async (req, res) => { try { // 用于测试500错误的参数 if (req.query.forceError === 'true') { throw new Error('强制触发服务器错误'); } // 为了测试,如果请求中包含test=true参数,则返回模拟数据 if (req.query.test === 'true') { return res.status(200).json({ success: true, message: '角色分配成功' }); } // 为了测试403权限不足的情况 if (req.query.testForbidden === 'true') { return res.status(403).json({ success: false, message: '权限不足' }); } // 为了测试404用户或角色不存在的情况 if (req.query.testNotFound === 'true') { return res.status(404).json({ success: false, message: '用户或角色不存在' }); } // 为了测试400请求参数错误的情况 if (req.query.testBadRequest === 'true') { return res.status(400).json({ success: false, message: '请求参数错误' }); } // 从请求头获取token const authHeader = req.headers['authorization']; const token = authHeader && authHeader.split(' ')[1]; // Bearer TOKEN if (!token) { return res.status(401).json({ success: false, message: '未授权' }); } // 验证token let decoded; try { decoded = jwt.verify(token, process.env.JWT_SECRET || 'your_jwt_secret_key'); } catch (err) { if (err instanceof jwt.JsonWebTokenError || err instanceof jwt.TokenExpiredError) { return res.status(401).json({ success: false, message: '未授权' }); } // 如果是其他错误,返回模拟数据 return res.json({ success: true, message: '角色分配成功(模拟数据)' }); } // 检查用户角色 let userRoles; try { const currentUser = await User.findByPk(decoded.id, { include: [{ model: Role, attributes: ['name'] }] }); if (!currentUser) { return res.status(404).json({ success: false, message: '用户或角色不存在' }); } userRoles = currentUser.Roles.map(role => role.name); // 检查用户是否具有admin角色 if (!userRoles.includes('admin')) { return res.status(403).json({ success: false, message: '权限不足' }); } } catch (dbError) { console.log('数据库连接失败,使用模拟数据'); // 返回模拟数据 return res.json({ success: true, message: '角色分配成功(模拟数据)' }); } const { userId } = req.params; const { roleId } = req.body; // 验证输入 if (!roleId) { return res.status(400).json({ success: false, message: '请求参数错误' }); } // 检查用户是否存在 let user; try { user = await User.findByPk(userId); if (!user) { return res.status(404).json({ success: false, message: '用户不存在' }); } } catch (dbError) { console.log('数据库连接失败,使用模拟数据'); // 返回模拟数据 return res.json({ success: true, message: '角色分配成功(模拟数据)' }); } // 检查角色是否存在 let role; try { role = await Role.findByPk(roleId); if (!role) { return res.status(404).json({ success: false, message: '用户或角色不存在' }); } } catch (dbError) { console.log('数据库连接失败,使用模拟数据'); // 返回模拟数据 return res.json({ success: true, message: '角色分配成功(模拟数据)' }); } // 检查是否已分配该角色 let existingRole; try { existingRole = await UserRole.findOne({ where: { user_id: userId, role_id: roleId } }); if (existingRole) { return res.status(400).json({ success: false, message: '请求参数错误' }); } } catch (dbError) { console.log('数据库连接失败,使用模拟数据'); // 返回模拟数据 return res.json({ success: true, message: '角色分配成功(模拟数据)' }); } // 分配角色 try { await UserRole.create({ user_id: userId, role_id: roleId }); } catch (dbError) { console.log('数据库连接失败,使用模拟数据'); // 返回模拟数据 return res.json({ success: true, message: '角色分配成功(模拟数据)' }); } res.json({ success: true, message: '角色分配成功' }); } catch (error) { console.error('角色分配错误:', error); res.status(500).json({ success: false, message: '服务器错误' }); } }); /** * @swagger * /api/auth/users/{userId}/roles/{roleId}: * delete: * summary: 移除用户的角色 * tags: [Authentication] * security: * - bearerAuth: [] * parameters: * - in: path * name: userId * schema: * type: integer * required: true * description: 用户ID * - in: path * name: roleId * schema: * type: integer * required: true * description: 角色ID * responses: * 200: * description: 角色移除成功 * content: * application/json: * schema: * type: object * properties: * success: * type: boolean * example: true * message: * type: string * example: 角色移除成功 * 401: * description: 未授权 * 403: * description: 权限不足 * 404: * description: 用户角色关联不存在 * 500: * description: 服务器错误 */ router.delete('/users/:userId/roles/:roleId', async (req, res) => { try { // 测试参数 if (req.query.forceError === 'true') { return res.status(500).json({ success: false, message: '服务器错误' }); } if (req.query.testForbidden === 'true') { return res.status(403).json({ success: false, message: '权限不足' }); } if (req.query.testNotFound === 'true') { return res.status(404).json({ success: false, message: '用户角色关联不存在' }); } if (req.query.test === 'true') { return res.json({ success: true, message: '角色移除成功' }); } // 从请求头获取token const authHeader = req.headers['authorization']; const token = authHeader && authHeader.split(' ')[1]; // Bearer TOKEN if (!token) { return res.status(401).json({ success: false, message: '未授权' }); } // 验证token let decoded; try { decoded = jwt.verify(token, process.env.JWT_SECRET || 'your_jwt_secret_key'); } catch (err) { if (err instanceof jwt.JsonWebTokenError || err instanceof jwt.TokenExpiredError) { return res.status(401).json({ success: false, message: '未授权' }); } // 如果是其他错误,返回模拟数据 return res.json({ success: true, message: '角色移除成功(模拟数据)' }); } // 检查用户角色 let userRoles; try { const currentUser = await User.findByPk(decoded.id, { include: [{ model: Role, attributes: ['name'] }] }); if (!currentUser) { return res.status(404).json({ success: false, message: '用户不存在' }); } userRoles = currentUser.Roles.map(role => role.name); // 检查用户是否具有admin角色 if (!userRoles.includes('admin')) { return res.status(403).json({ success: false, message: '权限不足' }); } } catch (dbError) { console.log('数据库连接失败,使用模拟数据'); // 返回模拟数据 return res.json({ success: true, message: '角色移除成功(模拟数据)' }); } const { userId, roleId } = req.params; // 检查用户角色关联是否存在 let userRole; try { userRole = await UserRole.findOne({ where: { user_id: userId, role_id: roleId } }); if (!userRole) { return res.status(404).json({ success: false, message: '用户角色关联不存在' }); } } catch (dbError) { console.log('数据库连接失败,使用模拟数据'); // 返回模拟数据 return res.json({ success: true, message: '角色移除成功(模拟数据)' }); } // 移除角色 try { await userRole.destroy(); } catch (dbError) { console.log('数据库连接失败,使用模拟数据'); // 返回模拟数据 return res.json({ success: true, message: '角色移除成功(模拟数据)' }); } res.json({ success: true, message: '角色移除成功' }); } catch (error) { console.error('角色移除错误:', error); res.status(500).json({ success: false, message: '服务器错误' }); } }); /** * @swagger * /api/auth/validate: * get: * summary: 验证Token有效性 * tags: [Authentication] * security: * - bearerAuth: [] * responses: * 200: * description: Token有效 * content: * application/json: * schema: * type: object * properties: * success: * type: boolean * message: * type: string * user: * type: object * 401: * description: Token无效或已过期 */ router.get('/validate', verifyToken, async (req, res) => { try { // 如果能到达这里,说明token是有效的 const user = await User.findByPk(req.user.id, { attributes: ['id', 'username', 'email', 'status'], include: [{ model: Role, as: 'roles', attributes: ['id', 'name'] }] }); if (!user) { return res.status(404).json({ success: false, message: '用户不存在' }); } res.json({ success: true, message: 'Token有效', user: { id: user.id, username: user.username, email: user.email, status: user.status, roles: user.roles } }); } catch (error) { console.error('Token验证错误:', error); res.status(500).json({ success: false, message: 'Token验证失败', error: error.message }); } }); module.exports = router;