< 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
96%
Covered lines: 100
Uncovered lines: 4
Coverable lines: 104
Total lines: 210
Line coverage: 96.1%
Branch coverage
92%
Covered branches: 26
Total branches: 28
Branch coverage: 92.8%
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%141494.28%
RegisterAsync()100%4490.47%
ValidateTokenAsync(...)50%22100%
GenerateToken(...)87.5%88100%

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;
 9using FAU.Logica.Validadores;
 10
 11namespace FAU.Logica.Services;
 12
 13public class AuthService : IAuthService
 14{
 15    private readonly IUsuarioRepository _usuarioRepository;
 16    private readonly IConfiguration _configuration;
 17    private readonly IAuditoriaService _auditoriaService;
 18    private readonly ICurrentUserContext _currentUser;
 2119    private readonly int _maxIntentosFallidos = 5;
 2120    private readonly int _tiempoBloqueoMinutos = 30;
 21
 2122    public AuthService(IUsuarioRepository usuarioRepository, IConfiguration configuration, IAuditoriaService auditoriaSe
 23    {
 2124        _usuarioRepository = usuarioRepository;
 2125        _configuration = configuration;
 2126        _auditoriaService = auditoriaService;
 2127        _currentUser = currentUser;
 2128    }
 29
 30    public async Task<(bool Success, string? Token, string? Error)> LoginAsync(string username, string password)
 31    {
 32        try
 33        {
 1134            var usuario = await _usuarioRepository.GetByUsernameWithRolesAsync(username);
 35
 1136            if (usuario == null)
 37            {
 138                return (false, null, "Usuario o contraseña incorrectos");
 39            }
 40
 41            // Verificar si el usuario está bloqueado
 1042            if (usuario.Estado == "bloqueado" && usuario.BloqueadoHasta.HasValue)
 43            {
 244                if (usuario.BloqueadoHasta.Value > DateTime.Now)
 45                {
 146                    var minutosRestantes = (usuario.BloqueadoHasta.Value - DateTime.Now).Minutes;
 147                    return (false, null, $"Usuario bloqueado. Intente nuevamente en {minutosRestantes} minutos");
 48                }
 49                else
 50                {
 51                    // Desbloquear usuario
 152                    usuario.Estado = "activo";
 153                    usuario.BloqueadoHasta = null;
 154                    usuario.IntentosFallidos = 0;
 155                    await _usuarioRepository.UpdateAsync(usuario);
 56                }
 57            }
 58
 59            // Verificar contraseña
 960            if (!BCrypt.Net.BCrypt.Verify(password, usuario.PasswordHash))
 61            {
 62                // Incrementar intentos fallidos
 363                usuario.IntentosFallidos++;
 64
 365                if (usuario.IntentosFallidos >= _maxIntentosFallidos)
 66                {
 167                    usuario.Estado = "bloqueado";
 168                    usuario.BloqueadoHasta = DateTime.Now.AddMinutes(_tiempoBloqueoMinutos);
 169                    await _usuarioRepository.UpdateAsync(usuario);
 170                    await _auditoriaService.LogAuditoriaAsync(usuario.Id, AccionEnum.ActualizarUsuario, ContextoEnum.Aut
 171                    return (false, null, $"Usuario bloqueado por {_tiempoBloqueoMinutos} minutos debido a múltiples inte
 72                }
 73
 274                await _usuarioRepository.UpdateAsync(usuario);
 275                return (false, null, "Usuario o contraseña incorrectos");
 76            }
 77
 78            // Login exitoso - resetear intentos fallidos
 679            if (usuario.IntentosFallidos > 0)
 80            {
 181                usuario.IntentosFallidos = 0;
 182                await _usuarioRepository.UpdateAsync(usuario);
 83            }
 84
 685            await _auditoriaService.LogAuditoriaAsync(
 686                usuario.Id,
 687                AccionEnum.IniciarSesion,
 688                ContextoEnum.Autenticacion,
 689                host: _currentUser.Host,
 690                detalle: new { username = usuario.Username, ip = _currentUser.Host });
 91
 92            // Generar token
 693            var token = GenerateToken(usuario);
 94
 695            return (true, token, null);
 96        }
 097        catch (Exception ex)
 98        {
 099            return (false, null, $"Error durante el login: {ex.Message}");
 100        }
 11101    }
 102
 103    public async Task<(bool Success, Usuario? Usuario, string? Error)> RegisterAsync(string username, string password, l
 104    {
 105        try
 106        {
 107            // Verificar si el usuario ya existe
 5108            if (await _usuarioRepository.ExistsAsync(username))
 109            {
 1110                return (false, null, "El nombre de usuario ya está en uso");
 111            }
 112
 113            // Validar la contraseña (política unificada)
 4114            var (isValid, error) = PasswordPolicy.Validate(password);
 4115            if (!isValid)
 116            {
 3117                return (false, null, error);
 118            }
 119
 120            // Hash de la contraseña
 1121            var passwordHash = BCrypt.Net.BCrypt.HashPassword(password);
 122
 123            // Crear nuevo usuario
 1124            var nuevoUsuario = new Usuario
 1125            {
 1126                Username = username,
 1127                PasswordHash = passwordHash,
 1128                Estado = "activo",
 1129                IntentosFallidos = 0,
 1130                PersonaId = personaId,
 1131                FechaActualizacion = DateTime.Now
 1132            };
 133
 1134            var usuarioCreado = await _usuarioRepository.CreateAsync(nuevoUsuario);
 1135            await _auditoriaService.LogAuditoriaAsync(usuarioCreado.Id, AccionEnum.CrearUsuario, ContextoEnum.Autenticac
 136
 1137            return (true, usuarioCreado, null);
 138        }
 0139        catch (Exception ex)
 140        {
 0141            return (false, null, $"Error durante el registro: {ex.Message}");
 142        }
 5143    }
 144
 145    public Task<bool> ValidateTokenAsync(string token)
 146    {
 147        try
 148        {
 3149            var tokenHandler = new JwtSecurityTokenHandler();
 3150            var key = Encoding.UTF8.GetBytes(_configuration["Jwt:Key"] ?? throw new InvalidOperationException("JWT Key n
 151
 3152            tokenHandler.ValidateToken(token, new TokenValidationParameters
 3153            {
 3154                ValidateIssuerSigningKey = true,
 3155                IssuerSigningKey = new SymmetricSecurityKey(key),
 3156                ValidateIssuer = true,
 3157                ValidIssuer = _configuration["Jwt:Issuer"],
 3158                ValidateAudience = true,
 3159                ValidAudience = _configuration["Jwt:Audience"],
 3160                ValidateLifetime = true,
 3161                ClockSkew = TimeSpan.Zero
 3162            }, out SecurityToken validatedToken);
 163
 1164            return Task.FromResult(true);
 165        }
 2166        catch
 167        {
 2168            return Task.FromResult(false);
 169        }
 3170    }
 171
 172    public string GenerateToken(Usuario usuario)
 173    {
 9174        var key = Encoding.UTF8.GetBytes(_configuration["Jwt:Key"] ?? throw new InvalidOperationException("JWT Key no co
 9175        var tokenHandler = new JwtSecurityTokenHandler();
 176
 9177        var claims = new List<Claim>
 9178        {
 9179            new Claim(ClaimTypes.NameIdentifier, usuario.Id.ToString()),
 9180            new Claim(ClaimTypes.Name, usuario.Username),
 9181        };
 182
 183        // Agregar roles como claims
 22184        foreach (var rol in usuario.Roles)
 185        {
 2186            claims.Add(new Claim(ClaimTypes.Role, rol.Nombre));
 187
 188            // Agregar permisos del rol
 8189            foreach (var permiso in rol.Permisos)
 190            {
 2191                claims.Add(new Claim("Permission", permiso.Nombre));
 192            }
 193        }
 194
 9195        var tokenDescriptor = new SecurityTokenDescriptor
 9196        {
 9197            Subject = new ClaimsIdentity(claims),
 9198            Expires = DateTime.Now.AddHours(int.Parse(_configuration["Jwt:ExpirationHours"] ?? "8")),
 9199            Issuer = _configuration["Jwt:Issuer"],
 9200            Audience = _configuration["Jwt:Audience"],
 9201            SigningCredentials = new SigningCredentials(
 9202                new SymmetricSecurityKey(key),
 9203                SecurityAlgorithms.HmacSha256Signature)
 9204        };
 205
 9206        var token = tokenHandler.CreateToken(tokenDescriptor);
 9207        return tokenHandler.WriteToken(token);
 208    }
 209}
 210