< Summary

Information
Class: FAU.Logica.Services.AuthService
Assembly: FAU.Logica
File(s): /home/runner/work/Sistema.Liquidacion.BE/Sistema.Liquidacion.BE/FAU.Logica/Services/AuthService.cs
Line coverage
81%
Covered lines: 87
Uncovered lines: 20
Coverable lines: 107
Total lines: 232
Line coverage: 81.3%
Branch coverage
75%
Covered branches: 33
Total branches: 44
Branch coverage: 75%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)100%11100%
LoginAsync()78.57%281458.62%
RegisterAsync()75%4485%
ValidateTokenAsync(...)50%22100%
GenerateToken(...)87.5%88100%
ValidarPassword(...)68.75%401654.54%

File(s)

/home/runner/work/Sistema.Liquidacion.BE/Sistema.Liquidacion.BE/FAU.Logica/Services/AuthService.cs

#LineLine coverage
 1using System.IdentityModel.Tokens.Jwt;
 2using System.Security.Claims;
 3using System.Text;
 4using FAU.DataAccess.Repositories;
 5using FAU.Entidades;
 6using Microsoft.Extensions.Configuration;
 7using Microsoft.IdentityModel.Tokens;
 8using BCrypt.Net;
 9
 10namespace FAU.Logica.Services;
 11
 12public class AuthService : IAuthService
 13{
 14    private readonly IUsuarioRepository _usuarioRepository;
 15    private readonly IConfiguration _configuration;
 16    private readonly IAuditoriaService _auditoriaService;
 1217    private readonly int _maxIntentosFallidos = 5;
 1218    private readonly int _tiempoBloqueoMinutos = 30;
 19
 1220    public AuthService(IUsuarioRepository usuarioRepository, IConfiguration configuration, IAuditoriaService auditoriaSe
 21    {
 1222        _usuarioRepository = usuarioRepository;
 1223        _configuration = configuration;
 1224        _auditoriaService = auditoriaService;
 1225    }
 26
 27    public async Task<(bool Success, string? Token, string? Error)> LoginAsync(string username, string password)
 28    {
 29        try
 30        {
 531            var usuario = await _usuarioRepository.GetByUsernameWithRolesAsync(username);
 32
 533            if (usuario == null)
 34            {
 135                return (false, null, "Usuario o contraseña incorrectos");
 36            }
 37
 38            // Verificar si el usuario está bloqueado
 439            if (usuario.Estado == "bloqueado" && usuario.BloqueadoHasta.HasValue)
 40            {
 141                if (usuario.BloqueadoHasta.Value > DateTime.UtcNow)
 42                {
 143                    var minutosRestantes = (usuario.BloqueadoHasta.Value - DateTime.UtcNow).Minutes;
 144                    return (false, null, $"Usuario bloqueado. Intente nuevamente en {minutosRestantes} minutos");
 45                }
 46                else
 47                {
 48                    // Desbloquear usuario
 049                    usuario.Estado = "activo";
 050                    usuario.BloqueadoHasta = null;
 051                    usuario.IntentosFallidos = 0;
 052                    await _usuarioRepository.UpdateAsync(usuario);
 53                }
 54            }
 55
 56            // Verificar contraseña
 357            if (!BCrypt.Net.BCrypt.Verify(password, usuario.PasswordHash))
 58            {
 59                // Incrementar intentos fallidos
 160                usuario.IntentosFallidos++;
 61
 162                if (usuario.IntentosFallidos >= _maxIntentosFallidos)
 63                {
 064                    usuario.Estado = "bloqueado";
 065                    usuario.BloqueadoHasta = DateTime.UtcNow.AddMinutes(_tiempoBloqueoMinutos);
 066                    await _usuarioRepository.UpdateAsync(usuario);
 067                    return (false, null, $"Usuario bloqueado por {_tiempoBloqueoMinutos} minutos debido a múltiples inte
 68                }
 69
 170                await _usuarioRepository.UpdateAsync(usuario);
 171                return (false, null, "Usuario o contraseña incorrectos");
 72            }
 73
 74            // Login exitoso - resetear intentos fallidos
 275            if (usuario.IntentosFallidos > 0)
 76            {
 077                usuario.IntentosFallidos = 0;
 078                await _usuarioRepository.UpdateAsync(usuario);
 79            }
 80
 81            //log de un login exitoso
 82            //TODO: Sacar null
 283            _auditoriaService.LogAuditoria(usuario.Id, (int)AccionEnum.Login, (int)ContextoEnum.Autenticacion, null);  /
 84
 85            // Generar token
 286            var token = GenerateToken(usuario);
 87
 288            return (true, token, null);
 89        }
 090        catch (Exception ex)
 91        {
 092            return (false, null, $"Error durante el login: {ex.Message}");
 93        }
 594    }
 95
 96    public async Task<(bool Success, Usuario? Usuario, string? Error)> RegisterAsync(string username, string password, l
 97    {
 98        try
 99        {
 100            // Verificar si el usuario ya existe
 2101            if (await _usuarioRepository.ExistsAsync(username))
 102            {
 1103                return (false, null, "El nombre de usuario ya está en uso");
 104            }
 105
 106            // Validar la contraseña
 1107            var validacionPassword = ValidarPassword(password);
 1108            if (!validacionPassword.esValido)
 109            {
 0110                return (false, null, validacionPassword.mensaje);
 111            }
 112
 113            // Hash de la contraseña
 1114            var passwordHash = BCrypt.Net.BCrypt.HashPassword(password);
 115
 116            // Crear nuevo usuario
 1117            var nuevoUsuario = new Usuario
 1118            {
 1119                Username = username,
 1120                PasswordHash = passwordHash,
 1121                Estado = "activo",
 1122                IntentosFallidos = 0,
 1123                PersonaId = personaId,
 1124                FechaActualizacion = DateTime.UtcNow
 1125            };
 126
 1127            var usuarioCreado = await _usuarioRepository.CreateAsync(nuevoUsuario);
 128
 1129            return (true, usuarioCreado, null);
 130        }
 0131        catch (Exception ex)
 132        {
 0133            return (false, null, $"Error durante el registro: {ex.Message}");
 134        }
 2135    }
 136
 137    public Task<bool> ValidateTokenAsync(string token)
 138    {
 139        try
 140        {
 3141            var tokenHandler = new JwtSecurityTokenHandler();
 3142            var key = Encoding.UTF8.GetBytes(_configuration["Jwt:Key"] ?? throw new InvalidOperationException("JWT Key n
 143
 3144            tokenHandler.ValidateToken(token, new TokenValidationParameters
 3145            {
 3146                ValidateIssuerSigningKey = true,
 3147                IssuerSigningKey = new SymmetricSecurityKey(key),
 3148                ValidateIssuer = true,
 3149                ValidIssuer = _configuration["Jwt:Issuer"],
 3150                ValidateAudience = true,
 3151                ValidAudience = _configuration["Jwt:Audience"],
 3152                ValidateLifetime = true,
 3153                ClockSkew = TimeSpan.Zero
 3154            }, out SecurityToken validatedToken);
 155
 1156            return Task.FromResult(true);
 157        }
 2158        catch
 159        {
 2160            return Task.FromResult(false);
 161        }
 3162    }
 163
 164    public string GenerateToken(Usuario usuario)
 165    {
 5166        var key = Encoding.UTF8.GetBytes(_configuration["Jwt:Key"] ?? throw new InvalidOperationException("JWT Key no co
 5167        var tokenHandler = new JwtSecurityTokenHandler();
 168
 5169        var claims = new List<Claim>
 5170        {
 5171            new Claim(ClaimTypes.NameIdentifier, usuario.Id.ToString()),
 5172            new Claim(ClaimTypes.Name, usuario.Username),
 5173        };
 174
 175        // Agregar roles como claims
 14176        foreach (var rol in usuario.Roles)
 177        {
 2178            claims.Add(new Claim(ClaimTypes.Role, rol.Nombre));
 179
 180            // Agregar permisos del rol
 8181            foreach (var permiso in rol.Permisos)
 182            {
 2183                claims.Add(new Claim("Permission", permiso.Nombre));
 184            }
 185        }
 186
 5187        var tokenDescriptor = new SecurityTokenDescriptor
 5188        {
 5189            Subject = new ClaimsIdentity(claims),
 5190            Expires = DateTime.UtcNow.AddHours(int.Parse(_configuration["Jwt:ExpirationHours"] ?? "8")),
 5191            Issuer = _configuration["Jwt:Issuer"],
 5192            Audience = _configuration["Jwt:Audience"],
 5193            SigningCredentials = new SigningCredentials(
 5194                new SymmetricSecurityKey(key),
 5195                SecurityAlgorithms.HmacSha256Signature)
 5196        };
 197
 5198        var token = tokenHandler.CreateToken(tokenDescriptor);
 5199        return tokenHandler.WriteToken(token);
 200    }
 201
 202    private (bool esValido, string mensaje) ValidarPassword(string password)
 203    {
 1204        if (string.IsNullOrWhiteSpace(password))
 205        {
 0206            return (false, "La contraseña no puede estar vacía");
 207        }
 208
 1209        if (password.Length < 8)
 210        {
 0211            return (false, "La contraseña debe tener al menos 8 caracteres");
 212        }
 213
 1214        if (!password.Any(char.IsUpper))
 215        {
 0216            return (false, "La contraseña debe contener al menos una letra mayúscula");
 217        }
 218
 1219        if (!password.Any(char.IsLower))
 220        {
 0221            return (false, "La contraseña debe contener al menos una letra minúscula");
 222        }
 223
 1224        if (!password.Any(char.IsDigit))
 225        {
 0226            return (false, "La contraseña debe contener al menos un número");
 227        }
 228
 1229        return (true, string.Empty);
 230    }
 231}
 232