Basis: Certvia dev@a48c5fb als Fundament für Craftvia
Unveränderter Stand von certvia/dev (a48c5fb) plus Craftvia-Spezifikation und Brandbook unter docs/craftvia/. ISMS-Module werden im Folgecommit entfernt. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,65 @@
|
||||
import { NextResponse, type NextRequest } from "next/server";
|
||||
|
||||
/**
|
||||
* Route gate (Next.js 16 proxy, formerly middleware): redirects anonymous
|
||||
* visitors to /login. This is a UX-level gate only — authoritative checks
|
||||
* happen server-side via requireSession()/requirePermission().
|
||||
*/
|
||||
|
||||
// SEC2: die Wiederherstellungs-Abläufe müssen ohne Session erreichbar sein —
|
||||
// der Nutzer ist gerade ausgesperrt. Ihre Absicherung sind Rate-Limit,
|
||||
// Enumeration-Neutralität und single-use-Tokens, nicht dieses Gate.
|
||||
const PUBLIC_PATHS = ["/login", "/api/auth", "/forgot-password", "/reset", "/invite", "/verify-email", "/platform/login", "/api/platform-auth"];
|
||||
|
||||
// Plattform-Bereich (getrennte Session/Login): diese Routen werden über das
|
||||
// Plattform-Cookie gegatet und leiten anonyme Besucher auf /platform/login —
|
||||
// NICHT auf die Mandanten-Login-Maske. `/admin` deckt auch `/admin/<id>` ab.
|
||||
const PLATFORM_PATHS = ["/admin", "/admins", "/profile", "/platform", "/templates"];
|
||||
|
||||
const matchesAny = (pathname: string, paths: string[]) =>
|
||||
paths.some((p) => pathname === p || pathname.startsWith(p + "/"));
|
||||
|
||||
export function proxy(request: NextRequest) {
|
||||
const { pathname } = request.nextUrl;
|
||||
|
||||
if (matchesAny(pathname, PUBLIC_PATHS)) {
|
||||
return NextResponse.next();
|
||||
}
|
||||
|
||||
// Plattform-Routen: eigenes Cookie, eigener Login. Ohne Tenant-Cookie-Bezug, sonst
|
||||
// landet ein angemeldeter Plattform-Admin fälschlich auf der Mandanten-Login-Maske.
|
||||
if (matchesAny(pathname, PLATFORM_PATHS)) {
|
||||
const hasPlatformCookie =
|
||||
request.cookies.has("platform-authjs.session-token") ||
|
||||
request.cookies.has("__Secure-platform-authjs.session-token");
|
||||
if (!hasPlatformCookie) {
|
||||
return NextResponse.redirect(new URL("/platform/login", request.url));
|
||||
}
|
||||
return NextResponse.next();
|
||||
}
|
||||
|
||||
const hasSessionCookie =
|
||||
request.cookies.has("authjs.session-token") ||
|
||||
request.cookies.has("__Secure-authjs.session-token");
|
||||
|
||||
if (!hasSessionCookie) {
|
||||
const loginUrl = new URL("/login", request.url);
|
||||
if (pathname !== "/") loginUrl.searchParams.set("callbackUrl", pathname);
|
||||
return NextResponse.redirect(loginUrl);
|
||||
}
|
||||
|
||||
return NextResponse.next();
|
||||
}
|
||||
|
||||
export const config = {
|
||||
// Everything except static assets.
|
||||
//
|
||||
// Ergänzt um `webmanifest`: das PWA-Manifest muss auch ohne Session ausgeliefert
|
||||
// werden, sonst bekommt der Browser für /site.webmanifest die Login-Seite statt
|
||||
// JSON. Die Logo-/Favicon-Dateien sind über die Endungen bereits abgedeckt.
|
||||
// Bewusst KEINE Verzeichnis-Ausnahme für `assets/` — das ist zugleich die
|
||||
// App-Route des Asset-Inventars und muss hinter dem Gate bleiben.
|
||||
matcher: [
|
||||
"/((?!_next/static|_next/image|favicon.ico|site.webmanifest|.*\\.(?:svg|png|jpg|ico|webmanifest)).*)",
|
||||
],
|
||||
};
|
||||
Reference in New Issue
Block a user