import { getBaseUrl } from "@/lib/url"; /** * Hosts, die fuer diesen Request als "eigener Origin" gelten duerfen. * * Reihenfolge nach Verlaesslichkeit: * 1. X-Forwarded-Host — der oeffentliche Host, den der eigene Reverse-Proxy * (Caddy bzw. Traefik, siehe Caddyfile bzw. k8s/06-ingress.yaml) * weiterreicht. Caddy verwirft einen vom Client mitgeschickten * X-Forwarded-Host per Default; bei Traefik entscheidet darueber * "forwardedHeaders.trustedIPs" des EntryPoints — nur fuer dort * aufgefuehrte Absender werden eingehende X-Forwarded-*-Header * uebernommen, von allen anderen ersetzt. * 2. Host — der Host, den der Browser tatsaechlich adressiert hat. Das ist * die massgebliche Groesse fuer eine Same-Origin-Pruefung und genau der * Vergleich, den Next.js fuer Server Actions intern selbst anstellt. * 3. request.url — nur noch als Rueckfallebene. Next.js rekonstruiert diese * URL aus der internen Bind-Adresse des Servers, NICHT aus dem * Host-Header; sie traegt daher z.B. "localhost:3000", waehrend der * Browser "wabenhain.com" adressiert hat. * 4. NEXT_PUBLIC_SITE_URL (getBaseUrl) — die konfigurierte oeffentliche * Adresse, falls gesetzt. * * Warum der Host-Header hier sicher ist: eine fremde Seite kann bei einem * Cross-Site-Request weder Host noch X-Forwarded-Host bestimmen. Den * Host-Header setzt der Browser aus der Ziel-URL des Requests (also dem * eigenen Host), und eigene Header wie X-Forwarded-Host liessen sich nur * per fetch() setzen, was einen CORS-Preflight ausloest, den dieser * Endpunkt nicht beantwortet. Ein gefaelschter Cross-Site-POST traegt also * weiterhin zwangslaeufig einen fremden Origin und faellt durch. */ function trustedHosts(request: Request): string[] { const hosts: string[] = []; // Bei mehreren Proxies steht der urspruenglich vom Client adressierte Host // als ERSTER Eintrag einer kommaseparierten Kette. const forwardedHost = request.headers.get("x-forwarded-host"); if (forwardedHost) hosts.push(forwardedHost.split(",")[0].trim()); const host = request.headers.get("host"); if (host) hosts.push(host.trim()); try { hosts.push(new URL(request.url).host); } catch { // request.url ist in der Praxis immer parsebar; ein Fehler hier darf die // uebrigen Kandidaten nicht mitreissen. } try { hosts.push(new URL(getBaseUrl()).host); } catch { // Falsch gesetztes NEXT_PUBLIC_SITE_URL darf die Pruefung nicht sprengen. } return hosts.filter(Boolean); } /** * Same-Origin-Pruefung fuer zustandsaendernde app/api/.../route.ts-Handler. * * Next.js' eingebauter CSRF-Schutz (Origin-Header gegen Host geprueft) gilt * nur fuer Server Actions ("use server") — eigene Route-Handler wie * /api/checkout oder /api/admin/upload sind normale POST-Endpunkte ohne * diesen automatischen Schutz und muessen ihn sich selbst holen. Ohne diese * Pruefung liesse sich ein state-aendernder POST z.B. per *