BCards/src/BCards.Web/Middleware/PreviewTokenMiddleware.cs
2025-07-12 02:32:22 -03:00

121 lines
4.2 KiB
C#

using BCards.Web.Services;
using Microsoft.Extensions.Caching.Memory;
namespace BCards.Web.Middleware;
public class PreviewTokenMiddleware
{
private readonly RequestDelegate _next;
private readonly IMemoryCache _cache;
private readonly ILogger<PreviewTokenMiddleware> _logger;
public PreviewTokenMiddleware(RequestDelegate next, IMemoryCache cache, ILogger<PreviewTokenMiddleware> logger)
{
_next = next;
_cache = cache;
_logger = logger;
}
public async Task InvokeAsync(HttpContext context)
{
var path = context.Request.Path.Value;
var query = context.Request.Query;
// Verificar se é uma requisição de preview
if (path != null && path.StartsWith("/page/") && query.ContainsKey("preview"))
{
var previewToken = query["preview"].FirstOrDefault();
if (!string.IsNullOrEmpty(previewToken))
{
var result = await HandlePreviewRequest(context, previewToken);
if (!result)
{
context.Response.StatusCode = 404;
await context.Response.WriteAsync("Preview não encontrado ou expirado.");
return;
}
}
}
await _next(context);
}
private async Task<bool> HandlePreviewRequest(HttpContext context, string previewToken)
{
try
{
// Verificar rate limiting por IP
var clientIp = GetClientIpAddress(context);
var rateLimitKey = $"preview_rate_limit_{clientIp}";
if (_cache.TryGetValue(rateLimitKey, out int requestCount))
{
if (requestCount >= 10) // Máximo 10 requisições por minuto por IP
{
_logger.LogWarning("Rate limit exceeded for IP {IP} on preview token {Token}", clientIp, previewToken);
return false;
}
_cache.Set(rateLimitKey, requestCount + 1, TimeSpan.FromMinutes(1));
}
else
{
_cache.Set(rateLimitKey, 1, TimeSpan.FromMinutes(1));
}
// Verificar se o token é válido
var moderationService = context.RequestServices.GetService<IModerationService>();
if (moderationService == null)
{
_logger.LogError("ModerationService not found in DI container");
return false;
}
var page = await moderationService.GetPageByPreviewTokenAsync(previewToken);
if (page == null)
{
_logger.LogInformation("Invalid or expired preview token: {Token}", previewToken);
return false;
}
// Incrementar contador de visualizações
var incrementResult = await moderationService.IncrementPreviewViewAsync(page.Id);
if (!incrementResult)
{
_logger.LogWarning("Preview view limit exceeded for page {PageId}", page.Id);
return false;
}
// Adicionar informações do preview ao contexto
context.Items["IsPreview"] = true;
context.Items["PreviewPageId"] = page.Id;
context.Items["PreviewToken"] = previewToken;
_logger.LogInformation("Valid preview request for page {PageId} with token {Token}", page.Id, previewToken);
return true;
}
catch (Exception ex)
{
_logger.LogError(ex, "Error handling preview request with token {Token}", previewToken);
return false;
}
}
private string GetClientIpAddress(HttpContext context)
{
// Verificar cabeçalhos de proxy
var xForwardedFor = context.Request.Headers["X-Forwarded-For"].FirstOrDefault();
if (!string.IsNullOrEmpty(xForwardedFor))
{
return xForwardedFor.Split(',')[0].Trim();
}
var xRealIp = context.Request.Headers["X-Real-IP"].FirstOrDefault();
if (!string.IsNullOrEmpty(xRealIp))
{
return xRealIp;
}
return context.Connection.RemoteIpAddress?.ToString() ?? "unknown";
}
}