import { Injectable, UnauthorizedException, forwardRef, Inject } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import { Repository } from 'typeorm'; import { JwtService } from '@nestjs/jwt'; import * as bcrypt from 'bcryptjs'; import { User } from '../entities/user.entity'; import { LoginDto } from './dto/auth.dto'; import { RbacService } from '../rbac/rbac.service'; // 内存中的登录失败计数器(按IP+用户名) const loginAttempts = new Map(); const MAX_ATTEMPTS = 5; const LOCK_MINUTES = 15; @Injectable() export class AuthService { constructor( @InjectRepository(User) private userRepo: Repository, private jwtService: JwtService, @Inject(forwardRef(() => RbacService)) private rbacService: RbacService, ) {} async login(dto: LoginDto, ip?: string) { const attemptKey = `${ip || 'unknown'}:${dto.username}`; const attempt = loginAttempts.get(attemptKey); // 检查是否在锁定期 if (attempt?.lockedUntil && attempt.lockedUntil > new Date()) { const remaining = Math.ceil((attempt.lockedUntil.getTime() - Date.now()) / 60000); throw new UnauthorizedException(`账号已被临时锁定,请 ${remaining} 分钟后重试`); } const user = await this.userRepo.findOne({ where: { username: dto.username }, relations: ['roles'], }); if (!user) { this.recordFailedAttempt(attemptKey); throw new UnauthorizedException('用户名或密码错误'); } if (user.isArchived) { throw new UnauthorizedException('账号已失效,请联系管理员'); } const valid = await bcrypt.compare(dto.password, user.passwordHash); if (!valid) { this.recordFailedAttempt(attemptKey); const att = loginAttempts.get(attemptKey); const remaining = MAX_ATTEMPTS - (att?.count || 0); if (remaining > 0) { throw new UnauthorizedException(`用户名或密码错误,还剩 ${remaining} 次尝试机会`); } throw new UnauthorizedException(`登录失败次数过多,账号已被锁定 ${LOCK_MINUTES} 分钟`); } // 登录成功,清除失败计数 loginAttempts.delete(attemptKey); // 记录登录时间 user.lastLoginAt = new Date(); await this.userRepo.save(user); // 获取用户权限 const permissions = await this.rbacService.getUserPermissions(user.id); const isSuperAdmin = user.roles?.some( (role) => role.status === 1 && (role.name === '超管' || role.name === 'super_admin' || role.code === 'super_admin'), ) ?? false; const payload = { sub: user.id, username: user.username, permissions, isSuperAdmin }; // 获取角色名称列表 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) { const attempt = loginAttempts.get(key) || { count: 0 }; attempt.count++; if (attempt.count >= MAX_ATTEMPTS) { attempt.lockedUntil = new Date(Date.now() + LOCK_MINUTES * 60 * 1000); } loginAttempts.set(key, attempt); } async validateUser(payload: any) { return this.userRepo.findOne({ where: { id: payload.sub } }); } }