diff --git a/DrSousan.Api/Program.cs b/DrSousan.Api/Program.cs index 81f143f..27a86cd 100644 --- a/DrSousan.Api/Program.cs +++ b/DrSousan.Api/Program.cs @@ -60,6 +60,21 @@ builder.Services.AddSingleton(HtmlEncoder.Create(UnicodeRanges.All)); builder.Services.ConfigureHttpJsonOptions(opts => opts.SerializerOptions.ReferenceHandler = System.Text.Json.Serialization.ReferenceHandler.IgnoreCycles); +// Response compression (gzip + brotli) — origin nginx no longer sits behind a +// compressing CDN, so HTML/CSS/JS were served uncompressed (~80KB). Cuts payload ~75%. +builder.Services.AddResponseCompression(opts => +{ + opts.EnableForHttps = true; + opts.Providers.Add(); + opts.Providers.Add(); + opts.MimeTypes = Microsoft.AspNetCore.ResponseCompression.ResponseCompressionDefaults.MimeTypes + .Concat(new[] { "application/ld+json", "image/svg+xml" }); +}); +builder.Services.Configure( + o => o.Level = System.IO.Compression.CompressionLevel.Fastest); +builder.Services.Configure( + o => o.Level = System.IO.Compression.CompressionLevel.Fastest); + // ── Build ───────────────────────────────────────────────────────────────────── var app = builder.Build(); @@ -68,9 +83,31 @@ var app = builder.Build(); if (!app.Environment.IsDevelopment()) app.UseExceptionHandler("/error"); +app.UseResponseCompression(); + +// Baseline security headers on every response (safe defaults; no HSTS yet — the +// cert was just stabilised, so we avoid forcing HTTPS pinning until it's proven). +app.Use(async (ctx, next) => +{ + var h = ctx.Response.Headers; + h["X-Content-Type-Options"] = "nosniff"; + h["X-Frame-Options"] = "SAMEORIGIN"; + h["Referrer-Policy"] = "strict-origin-when-cross-origin"; + await next(); +}); + app.UseCors(); app.UseDefaultFiles(); // serves /admin/index.html for /admin/ (wwwroot/index.html deleted → no conflict with Razor Pages) -app.UseStaticFiles(); +app.UseStaticFiles(new StaticFileOptions +{ + // Uploaded files use immutable GUID names → safe to cache aggressively. + OnPrepareResponse = ctx => + { + var p = ctx.Context.Request.Path.Value ?? ""; + if (p.StartsWith("/uploads/", StringComparison.OrdinalIgnoreCase)) + ctx.Context.Response.Headers["Cache-Control"] = "public,max-age=2592000,immutable"; + } +}); app.UseAuthentication(); app.UseAuthorization();