HttpOnly Cookies y Filtros Avanzados

1. Seguridad: Cookies HttpOnly
Backend (ChatbotApi):
AuthController.cs
: Ahora setea una cookie HttpOnly, Secure y SameSite=Strict llamada X-Access-Token en lugar de devolver el token en el cuerpo de la respuesta.
AuthController.cs
: Añadido endpoint logout para invalidar la cookie.
Program.cs
: Configurado JwtBearer para leer el token desde la cookie si está presente.
Frontend (chatbot-admin):
apiClient.ts
: Configurado con withCredentials: true para enviar cookies automáticamente. Eliminado el interceptor de localStorage.
Login.tsx
: Eliminado manejo de token manual. Ahora solo comprueba éxito (200 OK).
App.tsx
: Refactorizado para comprobar autenticación mediante una petición a /api/admin/contexto al inicio, en lugar de leer localStorage.
2. Filtros y Búsqueda
Logs (
AdminController.cs
 &
LogsViewer.tsx
):
Implementado filtrado en servidor por Fecha Inicio, Fecha Fin y Búsqueda de texto.
Frontend actualizado con selectores de fecha y barra de búsqueda.
Contexto y Fuentes (
ContextManager.tsx
 &
SourceManager.tsx
):
Añadida barra de búsqueda en el cliente para filtrar rápidamente por nombre, valor o descripción.
This commit is contained in:
2025-12-05 14:03:27 -03:00
parent 7e9e3ba87e
commit 5c97614e4f
13 changed files with 1509 additions and 146 deletions

View File

@@ -76,12 +76,35 @@ namespace ChatbotApi.Controllers
}
[HttpGet("logs")]
public async Task<IActionResult> GetConversationLogs()
public async Task<IActionResult> GetConversationLogs(
[FromQuery] DateTime? startDate,
[FromQuery] DateTime? endDate,
[FromQuery] string? search)
{
// Limitamos a 200 para evitar sobrecarga
var logs = await _context.ConversacionLogs
var query = _context.ConversacionLogs.AsQueryable();
if (startDate.HasValue)
{
query = query.Where(l => l.Fecha >= startDate.Value);
}
if (endDate.HasValue)
{
// Ajustamos al final del día si es necesario, o asumimos fecha exacta
query = query.Where(l => l.Fecha <= endDate.Value);
}
if (!string.IsNullOrWhiteSpace(search))
{
query = query.Where(l =>
l.UsuarioMensaje.Contains(search) ||
l.BotRespuesta.Contains(search));
}
// Limitamos a 500 para evitar sobrecarga pero permitiendo ver resultados de búsqueda
var logs = await query
.OrderByDescending(log => log.Fecha)
.Take(200)
.Take(500)
.ToListAsync();
return Ok(logs);
}

View File

@@ -17,7 +17,8 @@ public class LoginRequest
[MaxLength(100)]
public required string Password { get; set; }
}
public class LoginResponse { public required string Token { 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]")]
@@ -41,12 +42,31 @@ public class AuthController : ControllerBase
if (user != null && await _userManager.CheckPasswordAsync(user, loginRequest.Password))
{
var token = GenerateJwtToken(user);
return Ok(new LoginResponse { Token = token });
// [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());
}
return Unauthorized("Credenciales inválidas.");
}
[HttpPost("logout")]
public IActionResult Logout()
{
Response.Cookies.Delete("X-Access-Token");
return Ok(new { message = "Sesión cerrada" });
}
#if DEBUG
// [SEGURIDAD] Endpoint solo para desarrollo
[HttpPost("setup-admin")]

View File

@@ -57,7 +57,7 @@ namespace ChatbotApi.Controllers
try
{
await _context.SaveChangesAsync();
_cache.Remove(CacheKey); // Invalidate cache
_cache.Remove(CacheKey); // Invalidar caché
}
catch (DbUpdateConcurrencyException)
{
@@ -82,7 +82,7 @@ namespace ChatbotApi.Controllers
systemPrompt.UpdatedAt = DateTime.UtcNow;
_context.SystemPrompts.Add(systemPrompt);
await _context.SaveChangesAsync();
_cache.Remove(CacheKey); // Invalidate cache
_cache.Remove(CacheKey); // Invalidar caché
return CreatedAtAction("GetSystemPrompt", new { id = systemPrompt.Id }, systemPrompt);
}
@@ -99,7 +99,7 @@ namespace ChatbotApi.Controllers
_context.SystemPrompts.Remove(systemPrompt);
await _context.SaveChangesAsync();
_cache.Remove(CacheKey); // Invalidate cache
_cache.Remove(CacheKey); // Invalidar caché
return NoContent();
}
@@ -117,7 +117,7 @@ namespace ChatbotApi.Controllers
systemPrompt.IsActive = !systemPrompt.IsActive;
systemPrompt.UpdatedAt = DateTime.UtcNow;
await _context.SaveChangesAsync();
_cache.Remove(CacheKey); // Invalidate cache
_cache.Remove(CacheKey); // Invalidar caché
return Ok(new { IsActive = systemPrompt.IsActive });
}