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 _logger; public PreviewTokenMiddleware(RequestDelegate next, IMemoryCache cache, ILogger 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 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(); 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"; } }