97 lines
2.6 KiB
TypeScript
97 lines
2.6 KiB
TypeScript
import { Injectable, UnauthorizedException, HttpException, HttpStatus } from '@nestjs/common';
|
|
import { JwtService } from '@nestjs/jwt';
|
|
import { InjectRepository } from '@nestjs/typeorm';
|
|
import { Repository } from 'typeorm';
|
|
import * as bcrypt from 'bcrypt';
|
|
import { Usuario } from '../usuario/entities/usuario.entity.js';
|
|
import { LoginDto } from './dto/login.dto.js';
|
|
|
|
interface LoginAttempt {
|
|
count: number;
|
|
blockedUntil: Date | null;
|
|
}
|
|
|
|
@Injectable()
|
|
export class AuthService {
|
|
private loginAttempts = new Map<string, LoginAttempt>();
|
|
private readonly MAX_ATTEMPTS = 5;
|
|
private readonly BLOCK_DURATION_MS = 60 * 60 * 1000; // 1 hora
|
|
|
|
constructor(
|
|
@InjectRepository(Usuario)
|
|
private usuarioRepo: Repository<Usuario>,
|
|
private jwtService: JwtService,
|
|
) {}
|
|
|
|
async login(dto: LoginDto) {
|
|
const attempts = this.loginAttempts.get(dto.usuario);
|
|
|
|
if (attempts?.blockedUntil && attempts.blockedUntil > new Date()) {
|
|
const remainingMs = attempts.blockedUntil.getTime() - Date.now();
|
|
const remainingMin = Math.ceil(remainingMs / 60000);
|
|
throw new HttpException(
|
|
`Cuenta bloqueada. Intenta de nuevo en ${remainingMin} minuto(s).`,
|
|
HttpStatus.TOO_MANY_REQUESTS,
|
|
);
|
|
}
|
|
|
|
const usuario = await this.usuarioRepo.findOne({
|
|
where: { usuario: dto.usuario },
|
|
});
|
|
|
|
if (!usuario) {
|
|
this.trackFailedAttempt(dto.usuario);
|
|
throw new UnauthorizedException('Credenciales inválidas');
|
|
}
|
|
|
|
const passwordValid = await bcrypt.compare(dto.password, usuario.password);
|
|
if (!passwordValid) {
|
|
this.trackFailedAttempt(dto.usuario);
|
|
throw new UnauthorizedException('Credenciales inválidas');
|
|
}
|
|
|
|
this.loginAttempts.delete(dto.usuario);
|
|
|
|
const payload = {
|
|
sub: usuario.idUsuario,
|
|
usuario: usuario.usuario,
|
|
rol: usuario.rol,
|
|
};
|
|
|
|
return {
|
|
token: this.jwtService.sign(payload),
|
|
usuario: {
|
|
idUsuario: usuario.idUsuario,
|
|
nombre: usuario.nombre,
|
|
usuario: usuario.usuario,
|
|
rol: usuario.rol,
|
|
},
|
|
};
|
|
}
|
|
|
|
private trackFailedAttempt(username: string) {
|
|
const current = this.loginAttempts.get(username);
|
|
|
|
if (!current) {
|
|
this.loginAttempts.set(username, {
|
|
count: 1,
|
|
blockedUntil: null,
|
|
});
|
|
return;
|
|
}
|
|
|
|
const newCount = current.count + 1;
|
|
|
|
if (newCount >= this.MAX_ATTEMPTS) {
|
|
this.loginAttempts.set(username, {
|
|
count: newCount,
|
|
blockedUntil: new Date(Date.now() + this.BLOCK_DURATION_MS),
|
|
});
|
|
} else {
|
|
this.loginAttempts.set(username, {
|
|
count: newCount,
|
|
blockedUntil: null,
|
|
});
|
|
}
|
|
}
|
|
}
|