using System.Text.Json; using SIGCM2.Application.Abstractions; using SIGCM2.Application.Abstractions.Persistence; using SIGCM2.Application.Abstractions.Security; using SIGCM2.Domain.Exceptions; namespace SIGCM2.Application.Auth.Login; public sealed class LoginCommandHandler : ICommandHandler { private readonly IUsuarioRepository _repository; private readonly IPasswordHasher _hasher; private readonly IJwtService _jwtService; public LoginCommandHandler( IUsuarioRepository repository, IPasswordHasher hasher, IJwtService jwtService) { _repository = repository; _hasher = hasher; _jwtService = jwtService; } public async Task 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); var refreshToken = Guid.NewGuid().ToString("N"); // opaque, not persisted in UDT-001 var permisos = JsonSerializer.Deserialize(usuario.PermisosJson) ?? Array.Empty(); return new LoginResponseDto( AccessToken: accessToken, RefreshToken: refreshToken, ExpiresIn: 3600, Usuario: new UsuarioDto( Id: usuario.Id, Nombre: $"{usuario.Nombre} {usuario.Apellido}".Trim(), Rol: usuario.Rol, Permisos: permisos ) ); } }