fix: preview token
All checks were successful
BCards Deployment Pipeline / Run Tests (push) Successful in 2s
BCards Deployment Pipeline / PR Validation (push) Has been skipped
BCards Deployment Pipeline / Build and Push Image (push) Successful in 15m55s
BCards Deployment Pipeline / Deploy to Production (ARM - OCI) (push) Successful in 2m19s
BCards Deployment Pipeline / Deploy to Staging (x86 - Local) (push) Has been skipped
BCards Deployment Pipeline / Cleanup Old Resources (push) Has been skipped
BCards Deployment Pipeline / Deployment Summary (push) Successful in 1s
All checks were successful
BCards Deployment Pipeline / Run Tests (push) Successful in 2s
BCards Deployment Pipeline / PR Validation (push) Has been skipped
BCards Deployment Pipeline / Build and Push Image (push) Successful in 15m55s
BCards Deployment Pipeline / Deploy to Production (ARM - OCI) (push) Successful in 2m19s
BCards Deployment Pipeline / Deploy to Staging (x86 - Local) (push) Has been skipped
BCards Deployment Pipeline / Cleanup Old Resources (push) Has been skipped
BCards Deployment Pipeline / Deployment Summary (push) Successful in 1s
This commit is contained in:
parent
930ce8dab3
commit
04f406f6bc
@ -962,9 +962,10 @@ public class AdminController : Controller
|
||||
if (pageItem == null || pageItem.UserId != user.Id)
|
||||
return Json(new { success = false, message = "Página não encontrada" });
|
||||
|
||||
// Só renovar token para páginas "Creating"
|
||||
if (pageItem.Status != ViewModels.PageStatus.Creating)
|
||||
return Json(new { success = false, message = "Token só pode ser renovado para páginas em desenvolvimento" });
|
||||
// Só renovar token para páginas "Creating" e "Rejected"
|
||||
if (pageItem.Status != ViewModels.PageStatus.Creating &&
|
||||
pageItem.Status != ViewModels.PageStatus.Rejected)
|
||||
return Json(new { success = false, message = "Token só pode ser renovado para páginas em desenvolvimento ou rejeitadas" });
|
||||
|
||||
try
|
||||
{
|
||||
@ -973,11 +974,11 @@ public class AdminController : Controller
|
||||
|
||||
var previewUrl = $"{Request.Scheme}://{Request.Host}/page/{pageItem.Category}/{pageItem.Slug}?preview={newToken}";
|
||||
|
||||
return Json(new {
|
||||
success = true,
|
||||
return Json(new {
|
||||
success = true,
|
||||
previewToken = newToken,
|
||||
previewUrl = previewUrl,
|
||||
expiresAt = DateTime.UtcNow.AddHours(4).ToString("yyyy-MM-dd HH:mm:ss")
|
||||
expiresAt = DateTime.UtcNow.AddMinutes(5).ToString("yyyy-MM-dd HH:mm:ss")
|
||||
});
|
||||
}
|
||||
catch (Exception ex)
|
||||
|
||||
@ -81,6 +81,10 @@ public class PageStatusMiddleware
|
||||
return;
|
||||
}
|
||||
|
||||
// LOG DETALHADO ANTES da comparação
|
||||
_logger.LogInformation("Token comparison for page {PageId} - Provided: {ProvidedToken}, DB Token: {DbToken}, DB Expiry: {DbExpiry}",
|
||||
page.Id, previewToken, page.PreviewToken, page.PreviewTokenExpiry);
|
||||
|
||||
if (previewToken != page.PreviewToken)
|
||||
{
|
||||
_logger.LogInformation($"User id: {userId} - Page {category}/{slug} preview token mismatch - provided: {previewToken}, expected: {page.PreviewToken}");
|
||||
|
||||
@ -1,121 +0,0 @@
|
||||
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";
|
||||
}
|
||||
}
|
||||
@ -30,7 +30,9 @@ public class UserPageRepository : IUserPageRepository
|
||||
|
||||
public async Task<UserPage?> GetByIdAsync(string id)
|
||||
{
|
||||
return await _pages.Find(x => x.Id == id && x.IsActive).FirstOrDefaultAsync();
|
||||
// Forçar leitura do primary para consultas críticas (preview tokens)
|
||||
return await _pages.WithReadPreference(ReadPreference.Primary)
|
||||
.Find(x => x.Id == id && x.IsActive).FirstOrDefaultAsync();
|
||||
}
|
||||
|
||||
public async Task<UserPage?> GetBySlugAsync(string category, string slug)
|
||||
@ -47,8 +49,10 @@ public class UserPageRepository : IUserPageRepository
|
||||
{
|
||||
Collation = collation
|
||||
};
|
||||
|
||||
var cursor = await _pages.FindAsync(filter, findOptions);
|
||||
|
||||
// Forçar leitura do primary para consultas críticas (preview tokens)
|
||||
var cursor = await _pages.WithReadPreference(ReadPreference.Primary)
|
||||
.FindAsync(filter, findOptions);
|
||||
return await cursor.FirstOrDefaultAsync();
|
||||
}
|
||||
|
||||
|
||||
@ -27,15 +27,29 @@ public class ModerationService : IModerationService
|
||||
public async Task<string> GeneratePreviewTokenAsync(string pageId)
|
||||
{
|
||||
var token = Guid.NewGuid().ToString("N")[..16];
|
||||
var expiry = DateTime.UtcNow.AddHours(4); // Token válido por 4 horas
|
||||
var expiry = DateTime.UtcNow.AddMinutes(5); // Token válido por 5 minutos
|
||||
|
||||
// LOG ANTES da busca
|
||||
_logger.LogInformation("Generating token for page {PageId} - searching page in DB", pageId);
|
||||
|
||||
var page = await _userPageRepository.GetByIdAsync(pageId);
|
||||
|
||||
// LOG TOKEN ANTERIOR
|
||||
_logger.LogInformation("Page {PageId} - Old token: {OldToken}, New token: {NewToken}",
|
||||
pageId, page.PreviewToken, token);
|
||||
|
||||
page.PreviewToken = token;
|
||||
page.PreviewTokenExpiry = expiry;
|
||||
page.PreviewViewCount = 0;
|
||||
|
||||
// LOG ANTES do update
|
||||
_logger.LogInformation("Updating page {PageId} with new token {Token}", pageId, token);
|
||||
|
||||
await _userPageRepository.UpdateAsync(page);
|
||||
|
||||
_logger.LogInformation("Generated preview token for page {PageId}", pageId);
|
||||
// LOG APÓS update
|
||||
_logger.LogInformation("Successfully updated page {PageId} with token {Token}", pageId, token);
|
||||
|
||||
return token;
|
||||
}
|
||||
|
||||
|
||||
@ -374,6 +374,52 @@
|
||||
}
|
||||
});
|
||||
|
||||
// Sistema de auto-refresh para tokens de preview
|
||||
let refreshInterval;
|
||||
let activePreviewPages = new Map(); // pageId -> {category, slug, windowRef}
|
||||
|
||||
// Iniciar auto-refresh a cada 4 minutos
|
||||
function startAutoRefresh() {
|
||||
if (refreshInterval) clearInterval(refreshInterval);
|
||||
refreshInterval = setInterval(async () => {
|
||||
if (activePreviewPages.size > 0) {
|
||||
console.log(`Auto-refreshing ${activePreviewPages.size} active preview tokens...`);
|
||||
for (const [pageId, pageData] of activePreviewPages) {
|
||||
if (!pageData.windowRef.closed) {
|
||||
await refreshPageToken(pageId, pageData);
|
||||
} else {
|
||||
// Aba fechada, remover do tracking
|
||||
activePreviewPages.delete(pageId);
|
||||
}
|
||||
}
|
||||
}
|
||||
}, 4 * 60 * 1000); // 4 minutos
|
||||
}
|
||||
|
||||
async function refreshPageToken(pageId, pageData) {
|
||||
try {
|
||||
// Usar o endpoint específico para refresh ao invés de generate
|
||||
const response = await fetch(`/Admin/RefreshPreviewToken/${pageId}`, {
|
||||
method: 'POST',
|
||||
headers: { 'RequestVerificationToken': document.querySelector('input[name="__RequestVerificationToken"]').value }
|
||||
});
|
||||
const result = await response.json();
|
||||
if (result.success) {
|
||||
// Atualizar URL da aba existente
|
||||
const newUrl = `${window.location.origin}/page/${pageData.category}/${pageData.slug}?preview=${result.previewToken}`;
|
||||
pageData.windowRef.location.href = newUrl;
|
||||
console.log(`Token refreshed for page ${pageId}: ${result.previewToken}`);
|
||||
} else {
|
||||
console.warn(`Failed to refresh token for page ${pageId}: ${result.message}`);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`Failed to refresh token for page ${pageId}:`, error);
|
||||
}
|
||||
}
|
||||
|
||||
// Iniciar auto-refresh quando a página carrega
|
||||
startAutoRefresh();
|
||||
|
||||
// Funções existentes (submitForModeration, openPreview, etc.)
|
||||
async function openPreview(pageId) {
|
||||
const button = event.target.closest('button');
|
||||
@ -388,7 +434,23 @@
|
||||
});
|
||||
const result = await response.json();
|
||||
if (result.success) {
|
||||
window.open(`${window.location.origin}/page/${category}/${slug}?preview=${result.previewToken}`, '_blank');
|
||||
// Delay de 500ms para garantir commit no MongoDB
|
||||
await new Promise(resolve => setTimeout(resolve, 500));
|
||||
|
||||
// Abrir nova aba e adicionar ao tracking
|
||||
const previewWindow = window.open(
|
||||
`${window.location.origin}/page/${category}/${slug}?preview=${result.previewToken}`,
|
||||
`preview_${pageId}` // Nome único para a aba
|
||||
);
|
||||
|
||||
// Adicionar ao tracking para auto-refresh
|
||||
activePreviewPages.set(pageId, {
|
||||
category: category,
|
||||
slug: slug,
|
||||
windowRef: previewWindow
|
||||
});
|
||||
|
||||
console.log(`Page ${pageId} added to active preview tracking`);
|
||||
} else {
|
||||
showToast(result.message || 'Erro ao gerar preview', 'error');
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user