using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Mvc; using Microsoft.IdentityModel.Tokens; using System.ComponentModel.DataAnnotations; using System.IdentityModel.Tokens.Jwt; using System.Security.Claims; using System.Text; using Microsoft.AspNetCore.RateLimiting; public class LoginRequest { [Required] [MaxLength(100)] public required string Username { get; set; } [Required] [MaxLength(100)] public required string Password { get; set; } } // [SEGURIDAD] LoginResponse ya no es necesario si usamos solo cookies, pero podriamos dejar un mensaje de exito. public class LoginResponse { public string Message { get; set; } = "Login exitoso"; } [ApiController] [Route("api/[controller]")] public class AuthController : ControllerBase { private readonly IConfiguration _configuration; private readonly UserManager _userManager; public AuthController(IConfiguration configuration, UserManager userManager) { _configuration = configuration; _userManager = userManager; } [HttpPost("login")] [EnableRateLimiting("login-limit")] public async Task Login([FromBody] LoginRequest loginRequest) { var user = await _userManager.FindByNameAsync(loginRequest.Username); if (user != null && await _userManager.CheckPasswordAsync(user, loginRequest.Password)) { var token = GenerateJwtToken(user); // [SEGURIDAD] Setear Cookie HttpOnly var cookieOptions = new CookieOptions { HttpOnly = true, Secure = Request.IsHttps, // Dinámico: true si es HTTPS, false si es HTTP SameSite = SameSiteMode.Lax, // Permite navegación entre puertos/IPs más facil Expires = DateTime.UtcNow.AddHours(8) }; Response.Cookies.Append("X-Access-Token", token, cookieOptions); return Ok(new LoginResponse()); } return Unauthorized("Credenciales inválidas."); } [HttpPost("logout")] public IActionResult Logout() { Response.Cookies.Delete("X-Access-Token"); return Ok(new { message = "Sesión cerrada" }); } [HttpGet("status")] [Microsoft.AspNetCore.Authorization.AllowAnonymous] public IActionResult GetStatus() { return Ok(new { isAuthenticated = User.Identity?.IsAuthenticated ?? false }); } #if DEBUG // [SEGURIDAD] Endpoint solo para desarrollo [HttpPost("setup-admin")] public async Task SetupAdminUser() { var adminUser = await _userManager.FindByNameAsync("admin"); if (adminUser == null) { adminUser = new IdentityUser { UserName = "admin", Email = "tecnica@eldia.com", }; // En producción usar Secrets, no hardcoded var result = await _userManager.CreateAsync(adminUser, "Diagonal423"); if (result.Succeeded) { return Ok("Usuario administrador creado exitosamente."); } return BadRequest(result.Errors); } return Ok("El usuario administrador ya existe."); } #endif private string GenerateJwtToken(IdentityUser user) { var jwtKey = _configuration["Jwt:Key"] ?? throw new InvalidOperationException("La clave JWT no está configurada."); var securityKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(jwtKey)); var credentials = new SigningCredentials(securityKey, SecurityAlgorithms.HmacSha256); var claims = new[] { new Claim(JwtRegisteredClaimNames.Sub, user.UserName!), new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()) }; var token = new JwtSecurityToken( issuer: _configuration["Jwt:Issuer"], audience: _configuration["Jwt:Audience"], claims: claims, expires: DateTime.UtcNow.AddHours(8), signingCredentials: credentials); return new JwtSecurityTokenHandler().WriteToken(token); } }