diff --git a/src/BCards.Web/Program.cs b/src/BCards.Web/Program.cs index ab331e9..fdcb9c5 100644 --- a/src/BCards.Web/Program.cs +++ b/src/BCards.Web/Program.cs @@ -539,11 +539,25 @@ if (!app.Environment.IsDevelopment()) app.Use(async (context, next) => { + // Security headers context.Response.Headers.Append("X-Content-Type-Options", "nosniff"); context.Response.Headers.Append("X-Frame-Options", "DENY"); context.Response.Headers.Append("Referrer-Policy", "no-referrer"); context.Response.Headers.Append("Permissions-Policy", "camera=(), microphone=(), geolocation=()"); + // Load balancer e debugging headers + context.Response.Headers.Append("X-Server-ID", Environment.MachineName); + context.Response.Headers.Append("X-Instance-ID", $"{Environment.MachineName}-{Environment.ProcessId}"); + + // Cloudflare information headers (quando disponível) + var cfCountry = context.Request.Headers["CF-IPCountry"].FirstOrDefault(); + var cfRay = context.Request.Headers["CF-RAY"].FirstOrDefault(); + + if (!string.IsNullOrEmpty(cfCountry)) + context.Response.Headers.Append("X-Country", cfCountry); + if (!string.IsNullOrEmpty(cfRay)) + context.Response.Headers.Append("X-CF-Ray", cfRay); + context.Response.OnStarting(() => { context.Response.Headers.Remove("Server"); @@ -571,27 +585,32 @@ app.UseStaticFiles(new StaticFileOptions var extension = Path.GetExtension(fileName).ToLowerInvariant(); TimeSpan maxAge; + string cacheControl; if (extension == ".css" || extension == ".js") { maxAge = TimeSpan.FromDays(30); + cacheControl = $"public, max-age={maxAge.TotalSeconds}, immutable"; } else if (extension == ".woff" || extension == ".woff2" || extension == ".ttf" || extension == ".eot" || extension == ".svg" || extension == ".otf") { maxAge = TimeSpan.FromDays(365); + cacheControl = $"public, max-age={maxAge.TotalSeconds}, immutable"; } else if (extension == ".png" || extension == ".jpg" || extension == ".jpeg" || extension == ".gif" || extension == ".ico" || extension == ".webp") { maxAge = TimeSpan.FromDays(180); + cacheControl = $"public, max-age={maxAge.TotalSeconds}"; } else { maxAge = TimeSpan.FromDays(1); + cacheControl = $"public, max-age={maxAge.TotalSeconds}"; } - ctx.Context.Response.Headers.CacheControl = $"public,max-age={maxAge.TotalSeconds}"; + ctx.Context.Response.Headers.CacheControl = cacheControl; ctx.Context.Response.Headers.Append("Vary", "Accept-Encoding"); } }); @@ -601,6 +620,36 @@ app.UseRequestLocalization(); app.UseAuthentication(); app.UseAuthorization(); +// Cache middleware para páginas dinâmicas +app.Use(async (context, next) => +{ + // Páginas user dinâmicas - sem cache + if (context.Request.Path.StartsWithSegments("/page/") || + context.Request.Path.StartsWithSegments("/Admin") || + context.Request.Path.StartsWithSegments("/Auth") || + context.Request.Path.StartsWithSegments("/Payment")) + { + context.Response.Headers.Append("Cache-Control", "private, no-store, must-revalidate"); + context.Response.Headers.Append("Pragma", "no-cache"); + context.Response.Headers.Append("Expires", "0"); + } + // API endpoints - sem cache + else if (context.Request.Path.StartsWithSegments("/api/") || + context.Request.Path.StartsWithSegments("/click/") || + context.Request.Path.StartsWithSegments("/health")) + { + context.Response.Headers.Append("Cache-Control", "no-cache, no-store, must-revalidate"); + context.Response.Headers.Append("Pragma", "no-cache"); + } + // Páginas públicas - cache moderado + else if (context.Request.Path.StartsWithSegments("/Home") || context.Request.Path.Value == "/") + { + context.Response.Headers.Append("Cache-Control", "public, max-age=300"); // 5 min + } + + await next(); +}); + app.UseMiddleware(); app.UseMiddleware(); app.UseMiddleware();