121 lines
4.2 KiB
C#
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";
|
|
}
|
|
} |