Files
Chatbot-ElDia/ChatbotApi/Constrollers/AuthController.cs
dmolinari 67e179441d feat: Añadidos de seguridad (Backend, Frontend e IA)
Implementación de medidas de seguridad críticas tras auditoría:

Backend (API & IA):
- Anti-Prompt Injection: Reestructuración de prompts con delimitadores XML y sanitización estricta de inputs (Tag Injection).
- Anti-SSRF: Implementación de servicio `UrlSecurity` para validar URLs y bloquear accesos a IPs internas/privadas en funciones de scraping.
- Moderación: Activación de `SafetySettings` en Gemini API.
- Infraestructura:
  - Configuración de Headers de seguridad (HSTS, CSP, NoSniff).
  - CORS restrictivo (solo métodos HTTP necesarios).
  - Rate Limiting global y política estricta para Login (5 req/min).
  - Timeouts en HttpClient para prevenir DoS.
- Auth: Endpoint `setup-admin` restringido exclusivamente a entorno Debug.

Frontend (React):
- Anti-XSS & Tabnabbing: Configuración de esquema estricto en `rehype-sanitize` y forzado de `rel="noopener noreferrer"` en enlaces.
- Validación de longitud de input en cliente.

IA:
- Se realiza afinación de contexto de preguntas.
2025-11-27 15:11:54 -03:00

97 lines
2.9 KiB
C#

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; }
}
public class LoginResponse { public required string Token { get; set; } }
[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")]
[EnableRateLimiting("login-limit")]
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);
return Ok(new LoginResponse { Token = token });
}
return Unauthorized("Credenciales inválidas.");
}
#if DEBUG
// [SEGURIDAD] Endpoint solo para desarrollo
[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",
};
// 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);
}
}