It was never used. The repository lives on a self-hosted Gitea, which Vercel's git integration cannot connect to at all — so the documented route amounted to "mirror to GitHub first", and nobody did. vercel.json is gone, and with it the branch in next.config.ts that switched off `output: "standalone"` when the VERCEL variable was present. That branch was the only functional trace; everything else was documentation and comments describing a second deployment path that did not exist. DEPLOYMENT.md loses its "two supported ways" framing and the whole Vercel section — about fifty lines. Several statements next to it were stale for a different reason and are corrected in the same pass: the outbound-firewall table still listed Supabase's pooler (the database is a container now, nothing leaves the server), the prerequisites still demanded an existing Supabase project, and the .env table still asked for a pooler connection string instead of the two new passwords. The nightly job is described as what it is — a container in docker-compose.yml — rather than as a replacement for Vercel Cron. Migrations keep their references: two comments from July mention Vercel Cron, and they describe what was true when they were written. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
72 lines
3.0 KiB
TypeScript
72 lines
3.0 KiB
TypeScript
import type { NextConfig } from "next";
|
|
|
|
// Report-only rather than enforcing, deliberately: the policy is derived from
|
|
// what this app is known to load — its own bundle, the self-hosted Nunito
|
|
// files from next/font, and nothing else — but an unenforced policy that logs
|
|
// violations is worth more than a guessed one that blanks the app for every
|
|
// HR user. Promote it to `Content-Security-Policy` once the reports come back
|
|
// clean.
|
|
//
|
|
// 'unsafe-inline' on script-src is not laziness: Next.js inlines its
|
|
// bootstrap and hydration payload as inline <script> tags, so a nonce-based
|
|
// policy means threading a per-request nonce through proxy.ts — a separate
|
|
// change, and the reason this starts in report-only.
|
|
function contentSecurityPolicy(): string {
|
|
// Die Anmeldung schickt ein Formular an /api/auth/signin/…, und von dort
|
|
// geht es per Weiterleitung zum Anmeldeserver. `form-action` gilt auch für
|
|
// die Weiterleitungen nach einem Formularversand — steht der Aussteller
|
|
// nicht darin, bricht der Browser die Anmeldung ab. Genau hier wäre die
|
|
// Nur-Bericht-Fassung später eine böse Überraschung.
|
|
let issuerOrigin = "";
|
|
try {
|
|
issuerOrigin = new URL(process.env.AUTH_MICROSOFT_ENTRA_ID_ISSUER ?? "").origin;
|
|
} catch {
|
|
issuerOrigin = "";
|
|
}
|
|
const formAction = ["'self'", issuerOrigin].filter(Boolean).join(" ");
|
|
|
|
return [
|
|
"default-src 'self'",
|
|
"script-src 'self' 'unsafe-inline'",
|
|
"style-src 'self' 'unsafe-inline'",
|
|
"img-src 'self' data: blob:",
|
|
"font-src 'self'",
|
|
// Die Anwendung spricht im Browser mit niemandem ausser sich selbst: die
|
|
// Datenbank erreicht nur der Server, und seit die API-Schicht weg ist,
|
|
// gibt es keine Gegenstelle mehr, die von aussen angesprochen würde.
|
|
"connect-src 'self'",
|
|
"frame-ancestors 'self'",
|
|
"base-uri 'self'",
|
|
`form-action ${formAction}`,
|
|
"object-src 'none'",
|
|
].join("; ");
|
|
}
|
|
|
|
const nextConfig: NextConfig = {
|
|
// Emits a self-contained .next/standalone server (only the deps actually
|
|
// used at runtime, no full node_modules) — what the Dockerfile copies in.
|
|
//
|
|
// Ohne Ausnahme: die Anwendung läuft in einem Container auf einem eigenen
|
|
// Server, und dort ist das der richtige Ausgabemodus.
|
|
output: "standalone",
|
|
// Baseline security headers (clickjacking, MIME-sniffing, referrer leakage,
|
|
// browser feature access) plus the report-only CSP described above.
|
|
async headers() {
|
|
return [
|
|
{
|
|
source: "/:path*",
|
|
headers: [
|
|
{ key: "X-Frame-Options", value: "SAMEORIGIN" },
|
|
{ key: "X-Content-Type-Options", value: "nosniff" },
|
|
{ key: "Referrer-Policy", value: "strict-origin-when-cross-origin" },
|
|
{ key: "Permissions-Policy", value: "camera=(), microphone=(), geolocation=()" },
|
|
{ key: "Strict-Transport-Security", value: "max-age=63072000; includeSubDomains" },
|
|
{ key: "Content-Security-Policy-Report-Only", value: contentSecurityPolicy() },
|
|
],
|
|
},
|
|
];
|
|
},
|
|
};
|
|
|
|
export default nextConfig;
|