2025-11-18 14:34:26 -03:00
|
|
|
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;
|
2025-11-27 15:11:54 -03:00
|
|
|
using Microsoft.AspNetCore.RateLimiting;
|
2025-11-18 14:34:26 -03:00
|
|
|
|
|
|
|
|
public class LoginRequest
|
|
|
|
|
{
|
|
|
|
|
[Required]
|
|
|
|
|
[MaxLength(100)]
|
|
|
|
|
public required string Username { get; set; }
|
|
|
|
|
|
|
|
|
|
[Required]
|
|
|
|
|
[MaxLength(100)]
|
|
|
|
|
public required string Password { get; set; }
|
|
|
|
|
}
|
2025-12-05 14:03:27 -03:00
|
|
|
// [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"; }
|
2025-11-18 14:34:26 -03:00
|
|
|
|
|
|
|
|
[ApiController]
|
|
|
|
|
[Route("api/[controller]")]
|
|
|
|
|
public class AuthController : ControllerBase
|
|
|
|
|
{
|
|
|
|
|
private readonly IConfiguration _configuration;
|
|
|
|
|
private readonly UserManager<IdentityUser> _userManager;
|
|
|
|
|
|
|
|
|
|
public AuthController(IConfiguration configuration, UserManager<IdentityUser> userManager)
|
|
|
|
|
{
|
|
|
|
|
_configuration = configuration;
|
|
|
|
|
_userManager = userManager;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[HttpPost("login")]
|
2025-11-27 15:11:54 -03:00
|
|
|
[EnableRateLimiting("login-limit")]
|
2025-11-18 14:34:26 -03:00
|
|
|
public async Task<IActionResult> Login([FromBody] LoginRequest loginRequest)
|
|
|
|
|
{
|
|
|
|
|
var user = await _userManager.FindByNameAsync(loginRequest.Username);
|
|
|
|
|
|
|
|
|
|
if (user != null && await _userManager.CheckPasswordAsync(user, loginRequest.Password))
|
|
|
|
|
{
|
|
|
|
|
var token = GenerateJwtToken(user);
|
2025-12-05 14:03:27 -03:00
|
|
|
|
|
|
|
|
// [SEGURIDAD] Setear Cookie HttpOnly
|
|
|
|
|
var cookieOptions = new CookieOptions
|
|
|
|
|
{
|
|
|
|
|
HttpOnly = true,
|
|
|
|
|
Secure = true, // Requiere HTTPS
|
|
|
|
|
SameSite = SameSiteMode.Strict,
|
|
|
|
|
Expires = DateTime.UtcNow.AddHours(8)
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
Response.Cookies.Append("X-Access-Token", token, cookieOptions);
|
|
|
|
|
|
|
|
|
|
return Ok(new LoginResponse());
|
2025-11-18 14:34:26 -03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return Unauthorized("Credenciales inválidas.");
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-05 14:03:27 -03:00
|
|
|
[HttpPost("logout")]
|
|
|
|
|
public IActionResult Logout()
|
|
|
|
|
{
|
|
|
|
|
Response.Cookies.Delete("X-Access-Token");
|
|
|
|
|
return Ok(new { message = "Sesión cerrada" });
|
|
|
|
|
}
|
|
|
|
|
|
2025-11-27 15:11:54 -03:00
|
|
|
#if DEBUG
|
|
|
|
|
// [SEGURIDAD] Endpoint solo para desarrollo
|
2025-11-18 14:34:26 -03:00
|
|
|
[HttpPost("setup-admin")]
|
|
|
|
|
public async Task<IActionResult> SetupAdminUser()
|
|
|
|
|
{
|
|
|
|
|
var adminUser = await _userManager.FindByNameAsync("admin");
|
|
|
|
|
if (adminUser == null)
|
|
|
|
|
{
|
|
|
|
|
adminUser = new IdentityUser
|
|
|
|
|
{
|
|
|
|
|
UserName = "admin",
|
|
|
|
|
Email = "tecnica@eldia.com",
|
|
|
|
|
};
|
2025-11-27 15:11:54 -03:00
|
|
|
// En producción usar Secrets, no hardcoded
|
2025-11-18 14:34:26 -03:00
|
|
|
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.");
|
|
|
|
|
}
|
2025-11-27 15:11:54 -03:00
|
|
|
#endif
|
2025-11-18 14:34:26 -03:00
|
|
|
|
|
|
|
|
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);
|
|
|
|
|
}
|
|
|
|
|
}
|