fix: Weiterleitungen innerhalb der Seite relativ statt auf die Produktionsdomain

Sechs Route-Handler (Hinweis-Weiterleitung, E-Mail-Bestaetigung,
Newsletter bestaetigen/abmelden, Entra-Login und -Callback) bauten ihr
Weiterleitungsziel mit getBaseUrl() absolut zusammen. Ohne gesetztes
NEXT_PUBLIC_SITE_URL (lokal, Staging, Vorschau-Host) sprang man damit auf
https://wabenhain.com, und die gerade gesetzte Flash-Meldung ging auf dem
fremden Host verloren - aufgefallen, als die Admin-Pruefung nach
/api/hinweis umleitete.

Neuer Helfer lib/redirect.ts (redirectWithinSite) mit relativem
Location-Header, analog zu redirect() in Server Actions. Er lehnt alles
ausser Pfaden dieser Seite ab ("//host", "/\host", absolute URLs), damit
daraus keine offene Weiterleitung werden kann. getSiteUrl() bleibt fuer
Adressen, die die Seite verlassen (E-Mails, Stripe, Entra-redirect_uri,
Sitemap, Canonicals).

proxy.ts bleibt bei new URL("/konto", request.url) (eigener Host): der
Proxy verlangt einen absoluten Location-Header, ein relativer endet dort
in "Invalid URL" - als Kommentar festgehalten.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
This commit is contained in:
Nick SzaunigandClaude Opus 5.5 committed 2026-09-26 01:11:44 +02:00
1 parent 4f442ec06d
commit 52ce37e216
9 files changed
+76 -29

No files matched your search

+28
View File
@@ -0,0 +1,28 @@
import { describe, test, expect } from "vitest";
import { redirectWithinSite } from "./redirect";
describe("redirectWithinSite", () => {
test("setzt einen relativen Location-Header statt einer absoluten Domain", () => {
const response = redirectWithinSite("/konto");
expect(response.status).toBe(307);
expect(response.headers.get("location")).toBe("/konto");
});
test("behaelt Query-Parameter bei", () => {
const response = redirectWithinSite("/newsletter-bestaetigt?status=ok");
expect(response.headers.get("location")).toBe("/newsletter-bestaetigt?status=ok");
});
test("laesst sich mit Cookies (Flash-Meldung) versehen", () => {
const response = redirectWithinSite("/konto");
response.cookies.set("probe", "1");
expect(response.headers.get("set-cookie")).toContain("probe=1");
});
test.each(["https://evil.example/konto", "//evil.example/konto", "konto", "/\\evil.example"])(
"lehnt %s ab (keine offene Weiterleitung)",
(target) => {
expect(() => redirectWithinSite(target)).toThrow();
}
);
});