using Microsoft.Extensions.Options; using SameBeat.Infrastructure.LiveKit; namespace SameBeat.Api.Middleware; /// /// Dodaje do KAZDEJ odpowiedzi (ETAP 7) naglowki bezpieczenstwa -- niezaleznie od tego, czy request /// trafil do kontrolera, huba SignalR, Swaggera czy do po /// nieobslugonym wyjatku. Dlatego rejestrowana JAKO PIERWSZA w potoku, zaraz po lapaczu wyjatkow. /// /// Content-Security-Policy jest CELOWO restrykcyjne (bez 'unsafe-inline' w script-src) -- adres /// LiveKit w connect-src jest wyciagany z konfiguracji (UstawieniaLiveKit.Url), NIE wpisany na sztywno, /// bo bedzie inny na produkcji niz lokalnie (patrz appsettings.json "LiveKit:Url"). /// public class NaglowkiBezpieczenstwaMiddleware { private readonly RequestDelegate _nastepny; private readonly IOptions _ustawieniaLiveKit; public NaglowkiBezpieczenstwaMiddleware(RequestDelegate nastepny, IOptions ustawieniaLiveKit) { _nastepny = nastepny; _ustawieniaLiveKit = ustawieniaLiveKit; } public async Task InvokeAsync(HttpContext kontekst) { // OnStarting -- naglowki musza byc dolozone TUZ PRZED wyslaniem odpowiedzi, dopiero gdy // znany jest jej ostateczny status (dziala tak samo dla 200, 4xx, 429 z rate limitera i 500 // z ObslugaWyjatkowMiddleware -- ta ostatnia siedzi PO tym middleware w potoku, wiec bez // OnStarting jej "Response.Clear()" wyczyscilby naglowki dodane "z gory"). kontekst.Response.OnStarting(() => { var naglowki = kontekst.Response.Headers; naglowki["Content-Security-Policy"] = ZbudujPolitykeCsp(); naglowki["X-Content-Type-Options"] = "nosniff"; naglowki["X-Frame-Options"] = "DENY"; naglowki["Referrer-Policy"] = "strict-origin-when-cross-origin"; return Task.CompletedTask; }); await _nastepny(kontekst); } private string ZbudujPolitykeCsp() { var urlLiveKit = _ustawieniaLiveKit.Value.Url; var zrodlaConnect = string.IsNullOrWhiteSpace(urlLiveKit) ? "'self'" : $"'self' {urlLiveKit}"; // style-src ma 'unsafe-inline' WYLACZNIE dla stylow -- Swagger UI (dostepny tylko w Development) // tego wymaga; script-src NIGDY nie dostaje 'unsafe-inline' (to byloby otwarcie na XSS). return string.Join("; ", "default-src 'self'", "script-src 'self'", "style-src 'self' 'unsafe-inline'", "img-src 'self' data:", "font-src 'self'", $"connect-src {zrodlaConnect}", "frame-ancestors 'none'", "base-uri 'self'", "form-action 'self'"); } }