代码评审 & 安全修复

后端:
- 创建 AdminGuard 替代 12 处手动 role 查库检查,统一用 JWT payload 中的 role
- 密码字段 select: false,所有需密码的查询显式 select('+password')
- 文件上传接口移除 @Public(),需 JWT 认证
- 管理员搜索关键词限长 50 字符防 ReDoS
- CORS 收窄,不再对非生产环境放行所有源
- postbuild 复制 certs 路径同步到 dist/src/certs
- package.json main/start:prod 路径更新为 dist/src/main

前端:
- resume.vue 文件上传补充 Authorization header
- login.vue 移除含用户邮箱的 console.log 日志
This commit is contained in:
yuzhiran
2026-06-11 19:55:10 +08:00
parent f7da843d56
commit 6dfb6bef48
14 changed files with 61 additions and 104 deletions
@@ -26,12 +26,12 @@ describe('UserService', () => {
}
beforeEach(async () => {
const chainable = (value: any) => ({ exec: jest.fn().mockResolvedValue(value), select: jest.fn().mockReturnThis() })
mockUserModel = {
findOne: jest.fn().mockReturnThis(),
findById: jest.fn().mockReturnThis(),
findByIdAndUpdate: jest.fn().mockReturnThis(),
findOne: jest.fn().mockReturnValue(chainable(null)),
findById: jest.fn().mockReturnValue(chainable(null)),
findByIdAndUpdate: jest.fn().mockReturnValue(chainable(null)),
create: jest.fn().mockResolvedValue(mockUser),
exec: jest.fn().mockResolvedValue(null),
}
mockJwtService = {
sign: jest.fn().mockReturnValue('mock-jwt-token'),
@@ -70,7 +70,7 @@ describe('UserService', () => {
})
it('should create user on first login and return token', async () => {
mockUserModel.findOne.mockReturnValueOnce({ exec: jest.fn().mockResolvedValue(null) })
mockUserModel.findOne.mockReturnValueOnce({ exec: jest.fn().mockResolvedValue(null), select: jest.fn().mockReturnThis() })
await service.sendCode('13800138000')
const result = await service.loginByPhone('13800138000', '123456')
expect(result).toHaveProperty('token', 'mock-jwt-token')
@@ -79,7 +79,7 @@ describe('UserService', () => {
})
it('should login existing user', async () => {
mockUserModel.findOne.mockReturnValueOnce({ exec: jest.fn().mockResolvedValue(mockUser) })
mockUserModel.findOne.mockReturnValueOnce({ exec: jest.fn().mockResolvedValue(mockUser), select: jest.fn().mockReturnThis() })
await service.sendCode('13800138000')
const result = await service.loginByPhone('13800138000', '123456')
expect(result).toHaveProperty('token')
@@ -105,7 +105,7 @@ describe('UserService', () => {
})
it('should login with valid email code', async () => {
mockUserModel.findOne.mockReturnValueOnce({ exec: jest.fn().mockResolvedValue(mockUser) })
mockUserModel.findOne.mockReturnValueOnce({ exec: jest.fn().mockResolvedValue(mockUser), select: jest.fn().mockReturnThis() })
const spy = jest.spyOn(mockEmailService, 'sendVerificationCode')
await service.sendEmailCode('test@example.com')
@@ -119,7 +119,7 @@ describe('UserService', () => {
describe('loginByPassword', () => {
it('should throw for nonexistent user', async () => {
mockUserModel.findOne.mockReturnValueOnce({ exec: jest.fn().mockResolvedValue(null) })
mockUserModel.findOne.mockReturnValueOnce({ exec: jest.fn().mockResolvedValue(null), select: jest.fn().mockReturnThis() })
await expect(service.loginByPassword('test@example.com', 'pass'))
.rejects.toThrow(HttpException)
})