Files
SIG-CM2.0/src/api/SIGCM2.Application/Auth/Login/LoginCommandHandler.cs

82 lines
3.2 KiB
C#
Raw Normal View History

using SIGCM2.Application.Abstractions;
using SIGCM2.Application.Abstractions.Persistence;
using SIGCM2.Application.Abstractions.Security;
using SIGCM2.Domain.Entities;
using SIGCM2.Domain.Exceptions;
using SIGCM2.Domain.Security;
namespace SIGCM2.Application.Auth.Login;
public sealed class LoginCommandHandler : ICommandHandler<LoginCommand, LoginResponseDto>
{
private readonly IUsuarioRepository _repository;
private readonly IPasswordHasher _hasher;
private readonly IJwtService _jwtService;
private readonly IRefreshTokenRepository _refreshRepository;
private readonly IRefreshTokenGenerator _refreshGenerator;
private readonly IClientContext _clientContext;
private readonly AuthOptions _authOptions;
private readonly IRolPermisoRepository _rolPermisoRepository;
public LoginCommandHandler(
IUsuarioRepository repository,
IPasswordHasher hasher,
IJwtService jwtService,
IRefreshTokenRepository refreshRepository,
IRefreshTokenGenerator refreshGenerator,
IClientContext clientContext,
AuthOptions authOptions,
IRolPermisoRepository rolPermisoRepository)
{
_repository = repository;
_hasher = hasher;
_jwtService = jwtService;
_refreshRepository = refreshRepository;
_refreshGenerator = refreshGenerator;
_clientContext = clientContext;
_authOptions = authOptions;
_rolPermisoRepository = rolPermisoRepository;
}
public async Task<LoginResponseDto> Handle(LoginCommand command)
{
var usuario = await _repository.GetByUsernameAsync(command.Username);
// Deliberately vague — never reveal which check failed
if (usuario is null || !usuario.Activo)
throw new InvalidCredentialsException();
if (!_hasher.Verify(command.Password, usuario.PasswordHash))
throw new InvalidCredentialsException();
var accessToken = _jwtService.GenerateAccessToken(usuario);
// Generate and persist refresh token — only the hash hits the DB
var rawRefresh = _refreshGenerator.Generate();
var hash = TokenHasher.Sha256Base64Url(rawRefresh);
var now = DateTime.UtcNow;
var ttl = TimeSpan.FromDays(_authOptions.RefreshTokenDays);
var entity = RefreshToken.IssueForNewFamily(
usuario.Id, hash, now, ttl,
_clientContext.Ip, _clientContext.UserAgent);
await _refreshRepository.AddAsync(entity);
// UDT-006: permisos vienen de RolPermiso, no de Usuario.PermisosJson
// Usuario.PermisosJson queda reservado para UDT-008 (overrides por usuario)
var permisoEntities = await _rolPermisoRepository.GetByRolCodigoAsync(usuario.Rol);
var permisos = permisoEntities.Select(p => p.Codigo).ToArray();
return new LoginResponseDto(
AccessToken: accessToken,
RefreshToken: rawRefresh, // raw to client — never stored
ExpiresIn: _authOptions.AccessTokenMinutes * 60,
Usuario: new UsuarioDto(
Id: usuario.Id,
Nombre: $"{usuario.Nombre} {usuario.Apellido}".Trim(),
Rol: usuario.Rol,
Permisos: permisos
)
);
}
}