Der voran gegangene Fix (proxy.ts nach src/ verschoben, damit Next.js sie ueberhaupt erkennt) hat eine zweite, bislang tote Stelle live geschaltet: eine Cookie-Pruefung auf "refreshToken", die zu /login umleitet, wenn das Cookie fehlt - fuer /settings, /apiaries, /colonies, /queens. Dieses Cookie wird vom BACKEND gesetzt (eigene Domain, apicorebackend.* statt apicore.*, Pfad /api/v1/auth) und ist dem Browser zufolge nie Teil einer Anfrage an das Frontend - proxy.ts lief auf der Frontend-Domain und konnte dieses Cookie strukturell nie sehen. Solange die Datei am falschen Ort lag und nie ausgefuehrt wurde, blieb das folgenlos; sobald sie lief, hat sie jeden Aufruf dieser Seiten serverseitig zurueck zu /login geschickt, unabhaengig vom tatsaechlichen Login-Status. Die Cookie-Pruefung komplett entfernt statt repariert: es gibt keinen Weg, ein cross-origin gesetztes Cookie hier sichtbar zu machen, ohne seinen Schutz (Pfad-Scoping) aufzuweichen. Die echte Session lebt ohnehin nur clientseitig (Access-Token im Speicher, Refresh-Cookie nur fuer Backend-Aufrufe) - src/app/(app)/layout.tsx prueft das bereits korrekt und leitet bei Bedarf weiter. proxy.ts setzt jetzt nur noch die Security-Header. Lokal verifiziert: /settings/users ohne jedes Cookie liefert jetzt 200 statt eines Redirects, CSP zeigt weiterhin den korrekten Laufzeit-API_URL. Co-Authored-By: Claude Sonnet 5 <[email protected]>
73 lines
2.8 KiB
TypeScript
73 lines
2.8 KiB
TypeScript
import { NextResponse } from "next/server";
|
|
|
|
// There used to be a cookie-based redirect-to-/login check here, gated on
|
|
// the "refreshToken" cookie. It could never work: that cookie is set by the
|
|
// BACKEND's own origin (a different domain, apicorebackend.* vs apicore.*),
|
|
// scoped to /api/v1/auth - the browser never sends it on requests to the
|
|
// frontend, so this proxy (running on the frontend's origin) can never see
|
|
// it. The check always evaluated "no session", harmless while this file
|
|
// sat in the wrong directory and never ran at all, but once relocated so
|
|
// Next.js actually picked it up, it started force-redirecting every visit
|
|
// to /settings, /apiaries, /colonies, /queens straight back to /login,
|
|
// regardless of whether the user was genuinely logged in - login state for
|
|
// this app is only observable client-side (in-memory access token,
|
|
// cross-origin refresh cookie), which is exactly what
|
|
// src/app/(app)/layout.tsx already correctly redirects on. Removed rather
|
|
// than patched: there is no way to make a cross-origin cookie visible to
|
|
// this server without weakening the cookie itself (e.g. dropping its path
|
|
// scope), which isn't worth it for a redirect the client already handles.
|
|
|
|
// Deliberately computed here, not in next.config.ts: that file's headers()
|
|
// is resolved once at build time and frozen into the standalone output, so
|
|
// process.env.API_URL there is whatever was (or wasn't) set during the
|
|
// Docker build - never the container's actual runtime value. Proxy runs the
|
|
// Node.js runtime fresh on every request, so this reads the real deployed
|
|
// API_URL correctly instead of silently falling back to localhost forever.
|
|
function apiOrigin(): string {
|
|
try {
|
|
return new URL(process.env.API_URL ?? "http://localhost:3001").origin;
|
|
} catch {
|
|
return "";
|
|
}
|
|
}
|
|
|
|
function cspHeader(): string {
|
|
return `
|
|
default-src 'self';
|
|
script-src 'self' 'unsafe-inline';
|
|
style-src 'self' 'unsafe-inline';
|
|
img-src 'self' blob: data:;
|
|
font-src 'self';
|
|
connect-src 'self' ${apiOrigin()};
|
|
object-src 'none';
|
|
base-uri 'self';
|
|
form-action 'self';
|
|
frame-ancestors 'self';
|
|
upgrade-insecure-requests;
|
|
`
|
|
.replace(/\s{2,}/g, " ")
|
|
.trim();
|
|
}
|
|
|
|
function withSecurityHeaders(response: NextResponse): NextResponse {
|
|
response.headers.set("Content-Security-Policy", cspHeader());
|
|
response.headers.set("X-Frame-Options", "SAMEORIGIN");
|
|
response.headers.set("X-Content-Type-Options", "nosniff");
|
|
response.headers.set("Referrer-Policy", "no-referrer");
|
|
response.headers.set(
|
|
"Strict-Transport-Security",
|
|
"max-age=31536000; includeSubDomains",
|
|
);
|
|
return response;
|
|
}
|
|
|
|
export function proxy() {
|
|
return withSecurityHeaders(NextResponse.next());
|
|
}
|
|
|
|
export const config = {
|
|
matcher: [
|
|
"/((?!_next/static|_next/image|favicon.ico|sw.js|manifest.webmanifest|icon.svg).*)",
|
|
],
|
|
};
|