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'");
}
}