All checks were successful
Container-Image bauen und veröffentlichen / build-and-push (push) Successful in 1m17s
Weiterleitungen hingen am containerinternen Port 3000: Die Middleware baute sie aus req.nextUrl.origin. Sie wertet jetzt X-Forwarded-Host und -Proto aus, ersatzweise den Host-Header. NEXTAUTH_URL ist damit optional und wird nicht mehr auf localhost:3000 vorbelegt. Der Container legt beim ersten Start selbst einen Admin an. Ohne ADMIN_PASSWORD wird ein Zufallspasswort erzeugt und einmalig ins Log geschrieben; bisher musste seed-admin von Hand nachgeholt werden. Ein mit anderem NEXTAUTH_SECRET verschlüsseltes Cookie erzeugte bei jedem Request einen JWTSessionError samt Stacktrace. Die Middleware löscht das Cookie jetzt beim Umleiten, der Logger meldet den Fall in einer Zeile. Die Prüfung nutzt das type-Feld, da Klassennamen im Produktions-Build minifiziert sind. Getestet auf Port 8080: Weiterleitung nach localhost:8080/auth/signin, Passwort im Erststart-Log, veraltetes Cookie mit Max-Age=0 entfernt, keine Stacktraces mehr. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
84 lines
2.4 KiB
TypeScript
84 lines
2.4 KiB
TypeScript
import { NextResponse } from "next/server"
|
||
import type { NextRequest } from "next/server"
|
||
|
||
import { auth } from "@/lib/auth"
|
||
|
||
/** Cookies, unter denen Auth.js die Sitzung ablegt (http und https). */
|
||
const SESSION_COOKIES = [
|
||
"authjs.session-token",
|
||
"__Secure-authjs.session-token",
|
||
]
|
||
|
||
/**
|
||
* Ermittelt die vom Client tatsächlich verwendete Basis-URL.
|
||
*
|
||
* `req.nextUrl.origin` zeigt hinter einem Reverse-Proxy oder im Container auf
|
||
* den internen Port (3000) – Weiterleitungen landeten dadurch auf einer für den
|
||
* Benutzer nicht erreichbaren Adresse. Die Forwarded-Header haben Vorrang.
|
||
*/
|
||
function externalBaseUrl(req: NextRequest) {
|
||
const forwardedHost = req.headers.get("x-forwarded-host")?.split(",")[0]?.trim()
|
||
const host = forwardedHost || req.headers.get("host")?.trim()
|
||
|
||
if (!host) return req.nextUrl.origin
|
||
|
||
const forwardedProto = req.headers.get("x-forwarded-proto")?.split(",")[0]?.trim()
|
||
const protocol = forwardedProto || req.nextUrl.protocol.replace(":", "") || "http"
|
||
|
||
return `${protocol}://${host}`
|
||
}
|
||
|
||
/**
|
||
* Leitet zur Anmeldung um und entfernt dabei ein eventuell vorhandenes
|
||
* Sitzungs-Cookie. Ohne das bleibt ein mit einem anderen NEXTAUTH_SECRET
|
||
* verschlüsseltes Cookie im Browser liegen und erzeugt bei jedem Aufruf einen
|
||
* JWTSessionError ("no matching decryption secret"), bis es von Hand
|
||
* gelöscht wird.
|
||
*/
|
||
function redirectToSignIn(req: NextRequest) {
|
||
const target = new URL("/auth/signin", externalBaseUrl(req))
|
||
const response = NextResponse.redirect(target)
|
||
|
||
for (const name of SESSION_COOKIES) {
|
||
if (req.cookies.has(name)) {
|
||
response.cookies.set(name, "", { path: "/", maxAge: 0 })
|
||
}
|
||
}
|
||
|
||
return response
|
||
}
|
||
|
||
export default auth((req) => {
|
||
const { pathname } = req.nextUrl
|
||
|
||
// Öffentlich erreichbare Bereiche
|
||
if (
|
||
pathname === "/" ||
|
||
pathname.startsWith("/auth/") ||
|
||
pathname.startsWith("/api/auth/") ||
|
||
pathname.startsWith("/api/privacy-policies")
|
||
) {
|
||
return
|
||
}
|
||
|
||
if (pathname.startsWith("/admin") || pathname.startsWith("/api/users")) {
|
||
if (!req.auth) {
|
||
return redirectToSignIn(req)
|
||
}
|
||
|
||
const userRole = req.auth.user?.role
|
||
if (userRole !== "ADMIN" && userRole !== "SUPER_ADMIN") {
|
||
return redirectToSignIn(req)
|
||
}
|
||
}
|
||
})
|
||
|
||
export const config = {
|
||
matcher: [
|
||
/*
|
||
* Alle Pfade außer statischen Next.js-Dateien und dem Favicon.
|
||
*/
|
||
"/((?!_next/static|_next/image|favicon.ico).*)",
|
||
],
|
||
}
|