using Google.Apis.Auth; using OtpNet; using SIGCM.Application.DTOs; using SIGCM.Application.Interfaces; using SIGCM.Domain.Entities; using SIGCM.Domain.Interfaces; using System.Text; namespace SIGCM.Infrastructure.Services; public class AuthService : IAuthService { private readonly IUserRepository _userRepo; private readonly ITokenService _tokenService; public AuthService(IUserRepository userRepo, ITokenService tokenService) { _userRepo = userRepo; _tokenService = tokenService; } // Inicio de sesión estándar con usuario y contraseña public async Task LoginAsync(string username, string password) { var user = await _userRepo.GetByUsernameAsync(username); if (user == null) return new AuthResult { Success = false, ErrorMessage = "Credenciales inválidas" }; // Verificación de bloqueo de cuenta if (user.LockoutEnd.HasValue && user.LockoutEnd.Value > DateTime.UtcNow) return new AuthResult { Success = false, ErrorMessage = "Cuenta bloqueada temporalmente", IsLockedOut = true }; // Verificación de cuenta activa if (!user.IsActive) return new AuthResult { Success = false, ErrorMessage = "Cuenta desactivada" }; // Verificación de contraseña bool valid = BCrypt.Net.BCrypt.Verify(password, user.PasswordHash); if (!valid) { user.FailedLoginAttempts++; if (user.FailedLoginAttempts >= 5) user.LockoutEnd = DateTime.UtcNow.AddMinutes(15); await _userRepo.UpdateAsync(user); return new AuthResult { Success = false, ErrorMessage = "Credenciales inválidas" }; } // Si MFA está activo, no devolver token aún, pedir verificación if (user.IsMfaEnabled) { return new AuthResult { Success = true, RequiresMfa = true, UserId = user.Id }; } // Éxito: Reiniciar intentos y generar token user.FailedLoginAttempts = 0; user.LockoutEnd = null; user.LastLogin = DateTime.UtcNow; await _userRepo.UpdateAsync(user); return new AuthResult { Success = true, Token = _tokenService.GenerateToken(user), RequiresPasswordChange = user.MustChangePassword, UserId = user.Id }; } // Registro de nuevos usuarios (Public Web) public async Task RegisterAsync(string username, string email, string password) { if (await _userRepo.GetByUsernameAsync(username) != null) return new AuthResult { Success = false, ErrorMessage = "El usuario ya existe" }; if (await _userRepo.GetByEmailAsync(email) != null) return new AuthResult { Success = false, ErrorMessage = "El email ya está registrado" }; var user = new User { Username = username, Email = email, PasswordHash = BCrypt.Net.BCrypt.HashPassword(password), Role = "User", // Rol por defecto para la web pública CreatedAt = DateTime.UtcNow, MustChangePassword = false }; await _userRepo.CreateAsync(user); return new AuthResult { Success = true, Token = _tokenService.GenerateToken(user), UserId = user.Id }; } // Login mediante Google OAuth public async Task GoogleLoginAsync(string idToken) { try { var payload = await GoogleJsonWebSignature.ValidateAsync(idToken); var user = await _userRepo.GetByGoogleIdAsync(payload.Subject) ?? await _userRepo.GetByEmailAsync(payload.Email); if (user == null) { // Auto-registro mediante Google user = new User { Username = payload.Email.Split('@')[0], Email = payload.Email, GoogleId = payload.Subject, PasswordHash = "OAUTH_LOGIN_" + Guid.NewGuid().ToString(), // Hash dummy Role = "User", CreatedAt = DateTime.UtcNow, MustChangePassword = false }; user.Id = await _userRepo.CreateAsync(user); } else if (string.IsNullOrEmpty(user.GoogleId)) { // Vincular cuenta existente con Google user.GoogleId = payload.Subject; await _userRepo.UpdateAsync(user); } if (user.IsMfaEnabled) return new AuthResult { Success = true, RequiresMfa = true, UserId = user.Id }; return new AuthResult { Success = true, Token = _tokenService.GenerateToken(user), UserId = user.Id }; } catch (InvalidJwtException) { return new AuthResult { Success = false, ErrorMessage = "Token de Google inválido" }; } } // Genera un secreto para configurar MFA con aplicaciones tipo Google Authenticator public async Task GenerateMfaSecretAsync(int userId) { var user = await _userRepo.GetByIdAsync(userId); if (user == null) throw new Exception("Usuario no encontrado"); var secretBytes = KeyGeneration.GenerateRandomKey(20); var secret = Base32Encoding.ToString(secretBytes); user.MfaSecret = secret; await _userRepo.UpdateAsync(user); return secret; } // Verifica el código TOTP ingresado por el usuario public async Task VerifyMfaCodeAsync(int userId, string code) { var user = await _userRepo.GetByIdAsync(userId); if (user == null || string.IsNullOrEmpty(user.MfaSecret)) return false; var totp = new Totp(Base32Encoding.ToBytes(user.MfaSecret)); return totp.VerifyTotp(code, out _, new VerificationWindow(1, 1)); } // Activa o desactiva MFA para el usuario public async Task EnableMfaAsync(int userId, bool enabled) { var user = await _userRepo.GetByIdAsync(userId); if (user == null) return; user.IsMfaEnabled = enabled; await _userRepo.UpdateAsync(user); } }