< 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
94%
Covered lines: 101
Uncovered lines: 6
Coverable lines: 107
Total lines: 232
Line coverage: 94.3%
Branch coverage
90%
Covered branches: 40
Total branches: 44
Branch coverage: 90.9%
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()100%141493.1%
RegisterAsync()100%4490%
ValidateTokenAsync(...)50%22100%
GenerateToken(...)87.5%88100%
ValidarPassword(...)87.5%181681.81%

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;
 1917    private readonly int _maxIntentosFallidos = 5;
 1918    private readonly int _tiempoBloqueoMinutos = 30;
 19
 1920    public AuthService(IUsuarioRepository usuarioRepository, IConfiguration configuration, IAuditoriaService auditoriaSe
 21    {
 1922        _usuarioRepository = usuarioRepository;
 1923        _configuration = configuration;
 1924        _auditoriaService = auditoriaService;
 1925    }
 26
 27    public async Task<(bool Success, string? Token, string? Error)> LoginAsync(string username, string password)
 28    {
 29        try
 30        {
 931            var usuario = await _usuarioRepository.GetByUsernameWithRolesAsync(username);
 32
 933            if (usuario == null)
 34            {
 135                return (false, null, "Usuario o contraseña incorrectos");
 36            }
 37
 38            // Verificar si el usuario está bloqueado
 839            if (usuario.Estado == "bloqueado" && usuario.BloqueadoHasta.HasValue)
 40            {
 241                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
 149                    usuario.Estado = "activo";
 150                    usuario.BloqueadoHasta = null;
 151                    usuario.IntentosFallidos = 0;
 152                    await _usuarioRepository.UpdateAsync(usuario);
 53                }
 54            }
 55
 56            // Verificar contraseña
 757            if (!BCrypt.Net.BCrypt.Verify(password, usuario.PasswordHash))
 58            {
 59                // Incrementar intentos fallidos
 360                usuario.IntentosFallidos++;
 61
 362                if (usuario.IntentosFallidos >= _maxIntentosFallidos)
 63                {
 164                    usuario.Estado = "bloqueado";
 165                    usuario.BloqueadoHasta = DateTime.UtcNow.AddMinutes(_tiempoBloqueoMinutos);
 166                    await _usuarioRepository.UpdateAsync(usuario);
 167                    return (false, null, $"Usuario bloqueado por {_tiempoBloqueoMinutos} minutos debido a múltiples inte
 68                }
 69
 270                await _usuarioRepository.UpdateAsync(usuario);
 271                return (false, null, "Usuario o contraseña incorrectos");
 72            }
 73
 74            // Login exitoso - resetear intentos fallidos
 475            if (usuario.IntentosFallidos > 0)
 76            {
 177                usuario.IntentosFallidos = 0;
 178                await _usuarioRepository.UpdateAsync(usuario);
 79            }
 80
 81            //log de un login exitoso
 82            //TODO: Sacar null
 483            _auditoriaService.LogAuditoria(usuario.Id, (int)AccionEnum.Login, (int)ContextoEnum.Autenticacion, null);  /
 84
 85            // Generar token
 486            var token = GenerateToken(usuario);
 87
 488            return (true, token, null);
 89        }
 090        catch (Exception ex)
 91        {
 092            return (false, null, $"Error durante el login: {ex.Message}");
 93        }
 994    }
 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
 5101            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
 4107            var validacionPassword = ValidarPassword(password);
 4108            if (!validacionPassword.esValido)
 109            {
 3110                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        }
 5135    }
 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    {
 7166        var key = Encoding.UTF8.GetBytes(_configuration["Jwt:Key"] ?? throw new InvalidOperationException("JWT Key no co
 7167        var tokenHandler = new JwtSecurityTokenHandler();
 168
 7169        var claims = new List<Claim>
 7170        {
 7171            new Claim(ClaimTypes.NameIdentifier, usuario.Id.ToString()),
 7172            new Claim(ClaimTypes.Name, usuario.Username),
 7173        };
 174
 175        // Agregar roles como claims
 18176        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
 7187        var tokenDescriptor = new SecurityTokenDescriptor
 7188        {
 7189            Subject = new ClaimsIdentity(claims),
 7190            Expires = DateTime.UtcNow.AddHours(int.Parse(_configuration["Jwt:ExpirationHours"] ?? "8")),
 7191            Issuer = _configuration["Jwt:Issuer"],
 7192            Audience = _configuration["Jwt:Audience"],
 7193            SigningCredentials = new SigningCredentials(
 7194                new SymmetricSecurityKey(key),
 7195                SecurityAlgorithms.HmacSha256Signature)
 7196        };
 197
 7198        var token = tokenHandler.CreateToken(tokenDescriptor);
 7199        return tokenHandler.WriteToken(token);
 200    }
 201
 202    private (bool esValido, string mensaje) ValidarPassword(string password)
 203    {
 4204        if (string.IsNullOrWhiteSpace(password))
 205        {
 0206            return (false, "La contraseña no puede estar vacía");
 207        }
 208
 4209        if (password.Length < 8)
 210        {
 1211            return (false, "La contraseña debe tener al menos 8 caracteres");
 212        }
 213
 3214        if (!password.Any(char.IsUpper))
 215        {
 1216            return (false, "La contraseña debe contener al menos una letra mayúscula");
 217        }
 218
 2219        if (!password.Any(char.IsLower))
 220        {
 0221            return (false, "La contraseña debe contener al menos una letra minúscula");
 222        }
 223
 2224        if (!password.Any(char.IsDigit))
 225        {
 1226            return (false, "La contraseña debe contener al menos un número");
 227        }
 228
 1229        return (true, string.Empty);
 230    }
 231}
 232