diff --git a/backend/src/auth/auth.controller.ts b/backend/src/auth/auth.controller.ts index 207fb37..21338eb 100644 --- a/backend/src/auth/auth.controller.ts +++ b/backend/src/auth/auth.controller.ts @@ -1,17 +1,19 @@ -import { Controller, Post, Body, UseGuards, Get, Put, Delete, Param, Request, Req } from '@nestjs/common'; +import { Controller, Post, Body, Get, Request, Req, UseGuards } from '@nestjs/common'; import { AuthService } from './auth.service'; -import { LoginDto, RegisterDto } from './dto/auth.dto'; +import { LoginDto } from './dto/auth.dto'; import { JwtAuthGuard } from './guards/jwt-auth.guard'; import { OperationLogsService } from '../operation-logs/operation-logs.service'; import { extractRequestInfo } from '../common/request-utils'; import { Throttle } from '@nestjs/throttler'; +import { Public } from './decorators/public.decorator'; @Controller('auth') export class AuthController { constructor(private authService: AuthService, private logService: OperationLogsService) {} + @Public() @Post('login') - @Throttle({ default: { ttl: 60000, limit: 5 } }) // 登录接口:每分钟最多5次 + @Throttle({ default: { ttl: 60000, limit: 5 } }) async login(@Body() dto: LoginDto, @Req() req: any) { const { ipAddress, userAgent } = extractRequestInfo(req); try { @@ -33,53 +35,9 @@ export class AuthController { } } - @Post('register') - @UseGuards(JwtAuthGuard) - async register(@Body() dto: RegisterDto, @Request() req: any) { - const { ipAddress, userAgent } = extractRequestInfo(req); - const result = await this.authService.register(dto); - await this.logService.log({ userId: req.user?.id, username: req.user?.username, module: '账号', action: '创建账号', detail: `用户名: ${dto.username}, 姓名: ${dto.name}`, ipAddress, userAgent }); - return result; - } - @UseGuards(JwtAuthGuard) @Get('profile') getProfile(@Request() req: any) { return req.user; } - - // ---- 用户管理 ---- - - @UseGuards(JwtAuthGuard) - @Get('users') - findAllUsers() { - return this.authService.findAllUsers(); - } - - @UseGuards(JwtAuthGuard) - @Put('users/:id') - async updateUser(@Param('id') id: string, @Body() body: { name?: string; role?: string; isActive?: boolean; username?: string; allowedMenus?: string[] }, @Request() req: any) { - const { ipAddress, userAgent } = extractRequestInfo(req); - const result = await this.authService.updateUser(+id, body); - await this.logService.log({ userId: req.user?.id, username: req.user?.username, module: '账号', action: '更新账号', targetId: +id, targetType: 'user', detail: JSON.stringify(body), ipAddress, userAgent }); - return result; - } - - @UseGuards(JwtAuthGuard) - @Put('users/:id/password') - async resetPassword(@Param('id') id: string, @Body() body: { password: string }, @Request() req: any) { - const { ipAddress, userAgent } = extractRequestInfo(req); - const result = await this.authService.resetPassword(+id, body.password); - await this.logService.log({ userId: req.user?.id, username: req.user?.username, module: '账号', action: '重置密码', targetId: +id, targetType: 'user', ipAddress, userAgent }); - return result; - } - - @UseGuards(JwtAuthGuard) - @Delete('users/:id') - async removeUser(@Param('id') id: string, @Request() req: any) { - const { ipAddress, userAgent } = extractRequestInfo(req); - const result = await this.authService.removeUser(+id); - await this.logService.log({ userId: req.user?.id, username: req.user?.username, module: '账号', action: '删除账号', targetId: +id, targetType: 'user', ipAddress, userAgent }); - return result; - } } diff --git a/backend/src/auth/auth.module.ts b/backend/src/auth/auth.module.ts index ae72109..420bfa8 100644 --- a/backend/src/auth/auth.module.ts +++ b/backend/src/auth/auth.module.ts @@ -1,4 +1,4 @@ -import { Module, OnModuleInit } from '@nestjs/common'; +import { Module, forwardRef } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; import { JwtModule } from '@nestjs/jwt'; import { PassportModule } from '@nestjs/passport'; @@ -7,6 +7,7 @@ import { User } from '../entities/user.entity'; import { AuthService } from './auth.service'; import { AuthController } from './auth.controller'; import { JwtStrategy } from './strategies/jwt.strategy'; +import { RbacModule } from '../rbac/rbac.module'; @Module({ imports: [ @@ -20,14 +21,10 @@ import { JwtStrategy } from './strategies/jwt.strategy'; signOptions: { expiresIn: config.get('JWT_EXPIRES_IN', '4h') }, }), }), + forwardRef(() => RbacModule), ], controllers: [AuthController], providers: [AuthService, JwtStrategy], exports: [AuthService], }) -export class AuthModule implements OnModuleInit { - constructor(private authService: AuthService) {} - async onModuleInit() { - await this.authService.initAdmin(); - } -} +export class AuthModule {} diff --git a/backend/src/auth/auth.service.ts b/backend/src/auth/auth.service.ts index f1471e1..3578fa4 100644 --- a/backend/src/auth/auth.service.ts +++ b/backend/src/auth/auth.service.ts @@ -1,11 +1,11 @@ -import { Injectable, UnauthorizedException, BadRequestException, NotFoundException } from '@nestjs/common'; +import { Injectable, UnauthorizedException, forwardRef, Inject } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; -import { Repository, Not, MoreThan } from 'typeorm'; +import { Repository } from 'typeorm'; import { JwtService } from '@nestjs/jwt'; -import { ConfigService } from '@nestjs/config'; import * as bcrypt from 'bcryptjs'; import { User } from '../entities/user.entity'; -import { LoginDto, RegisterDto } from './dto/auth.dto'; +import { LoginDto } from './dto/auth.dto'; +import { RbacService } from '../rbac/rbac.service'; // 内存中的登录失败计数器(按IP+用户名) const loginAttempts = new Map(); @@ -17,24 +17,9 @@ export class AuthService { constructor( @InjectRepository(User) private userRepo: Repository, private jwtService: JwtService, - private configService: ConfigService, + @Inject(forwardRef(() => RbacService)) private rbacService: RbacService, ) {} - async register(dto: RegisterDto) { - const exists = await this.userRepo.findOne({ where: { username: dto.username } }); - if (exists) throw new UnauthorizedException('用户名已存在'); - const hash = await bcrypt.hash(dto.password, 10); - const user = this.userRepo.create({ - username: dto.username, - passwordHash: hash, - name: dto.name, - role: 'operator', - allowedMenus: (dto as any).allowedMenus ? JSON.stringify((dto as any).allowedMenus) : null as any, - }); - await this.userRepo.save(user); - return { message: '注册成功' }; - } - async login(dto: LoginDto, ip?: string) { const attemptKey = `${ip || 'unknown'}:${dto.username}`; const attempt = loginAttempts.get(attemptKey); @@ -45,7 +30,10 @@ export class AuthService { throw new UnauthorizedException(`账号已被临时锁定,请 ${remaining} 分钟后重试`); } - const user = await this.userRepo.findOne({ where: { username: dto.username } }); + const user = await this.userRepo.findOne({ + where: { username: dto.username }, + relations: ['roles'], + }); if (!user) { this.recordFailedAttempt(attemptKey); throw new UnauthorizedException('用户名或密码错误'); @@ -68,9 +56,24 @@ export class AuthService { // 记录登录时间 user.lastLoginAt = new Date(); await this.userRepo.save(user); - const payload = { sub: user.id, username: user.username, role: user.role }; - const allowedMenus = user.allowedMenus ? JSON.parse(user.allowedMenus) : null; - return { access_token: this.jwtService.sign(payload), user: { id: user.id, username: user.username, name: user.name, role: user.role, allowedMenus } }; + + // 获取用户权限 + const permissions = await this.rbacService.getUserPermissions(user.id); + const payload = { sub: user.id, username: user.username, permissions }; + + // 获取角色名称列表 + const roleNames = user.roles ? user.roles.filter(r => r.status === 1).map(r => r.name) : []; + + return { + access_token: this.jwtService.sign(payload), + user: { + id: user.id, + username: user.username, + name: user.name, + roles: roleNames, + permissions, + }, + }; } private recordFailedAttempt(key: string) { @@ -85,65 +88,4 @@ export class AuthService { async validateUser(payload: any) { return this.userRepo.findOne({ where: { id: payload.sub } }); } - - async initAdmin() { - const count = await this.userRepo.count(); - if (count === 0) { - const adminPassword = this.configService.get('ADMIN_PASSWORD', 'admin123'); - const hash = await bcrypt.hash(adminPassword, 10); - await this.userRepo.save(this.userRepo.create({ username: 'admin', passwordHash: hash, name: '管理员', role: 'admin' })); - console.log(`已创建默认管理员: admin / ${adminPassword === 'admin123' ? 'admin123 (请尽快修改!)' : '******'}`); - } - } - - // ---- 用户管理 CRUD ---- - - async findAllUsers() { - const users = await this.userRepo.find({ - select: ['id', 'username', 'name', 'role', 'isActive', 'allowedMenus', 'lastLoginAt', 'createdAt', 'updatedAt'], - order: { createdAt: 'DESC' }, - }); - return users.map(u => ({ - ...u, - allowedMenus: u.allowedMenus ? JSON.parse(u.allowedMenus) : null, - })); - } - - async updateUser(id: number, data: { name?: string; role?: string; isActive?: boolean; username?: string; allowedMenus?: string[] }) { - const user = await this.userRepo.findOne({ where: { id } }); - if (!user) throw new NotFoundException('用户不存在'); - if (user.username === 'admin' && data.role && data.role !== 'admin') { - throw new BadRequestException('不能修改默认管理员的角色'); - } - if (user.username === 'admin' && data.isActive === false) { - throw new BadRequestException('不能禁用默认管理员'); - } - if (data.username !== undefined && data.username !== user.username) { - const exists = await this.userRepo.findOne({ where: { username: data.username } }); - if (exists) throw new BadRequestException('用户名已存在'); - user.username = data.username; - } - if (data.name !== undefined) user.name = data.name; - if (data.role !== undefined) user.role = data.role; - if (data.isActive !== undefined) user.isActive = data.isActive; - if (data.allowedMenus !== undefined) user.allowedMenus = data.allowedMenus ? JSON.stringify(data.allowedMenus) : null as any; - await this.userRepo.save(user); - return { message: '更新成功' }; - } - - async resetPassword(id: number, newPassword: string) { - const user = await this.userRepo.findOne({ where: { id } }); - if (!user) throw new NotFoundException('用户不存在'); - user.passwordHash = await bcrypt.hash(newPassword, 10); - await this.userRepo.save(user); - return { message: '密码已重置' }; - } - - async removeUser(id: number) { - const user = await this.userRepo.findOne({ where: { id } }); - if (!user) throw new NotFoundException('用户不存在'); - if (user.username === 'admin') throw new BadRequestException('不能删除默认管理员'); - await this.userRepo.delete(id); - return { message: '用户已删除' }; - } } diff --git a/backend/src/auth/strategies/jwt.strategy.ts b/backend/src/auth/strategies/jwt.strategy.ts index 3f97e9f..6b71f8e 100644 --- a/backend/src/auth/strategies/jwt.strategy.ts +++ b/backend/src/auth/strategies/jwt.strategy.ts @@ -14,6 +14,10 @@ export class JwtStrategy extends PassportStrategy(Strategy) { } async validate(payload: any) { - return { id: payload.sub, username: payload.username, role: payload.role }; + return { + id: payload.sub, + username: payload.username, + permissions: payload.permissions || [], + }; } }