Fundament: atomare Mandanten-Transaktionen, iframe-Vorschau, Uploads bis 25 MB, DSGVO-Felder

- db.ts: tenantTransaction() – atomar auch bei RLS_ENFORCED=true (AsyncLocalStorage
  bindet Operationen an eine craftvia_app-Transaktion, Kontext einmal gesetzt,
  verschachtelte Aufrufe treten bei, fremder Mandant wird abgewiesen)
- services/context.ts: inTransaction(ctx, fn); imports/confirm.ts umgestellt
- next.config.ts: EMBEDDABLE_FILE_ROUTES mit frame-ancestors 'self'/SAMEORIGIN
  (PDF-Vorschau Prüfmaske), proxyClientMaxBodySize 26mb (Import bis 25 MB)
- test-rls-enforcement: RLS-URL-Default aus DATABASE_URL (Lane-DBs)
- dsgvo/pii-fields: 26 Personenreferenzen des Craftvia-Domänenmodells
- ARCHITEKTUR §4.8: Transaktions-, Header-, Upload-, Versions- und PII-Regeln
- Test test-tenant-transaction (Commit/Rollback/Fremdmandant/Verschachtelung),
  grün im Owner- und im RLS-Modus

Gate: tsc, lint, build, 31/31 Tests grün.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-09-14 12:29:57 +02:00
co-authored by Claude Opus 5
parent 3e16689b2f
commit 4a25f2cc3b
8 changed files with 243 additions and 13 deletions
+21 -4
View File
@@ -31,6 +31,13 @@ const csp = [
"object-src 'none'",
].join("; ");
// Same-origin embedding for inline document previews (PDF viewer in the import review
// mask, document previews). Everything else stays frame-ancestors 'none' / DENY.
const cspEmbeddable = csp.replace("frame-ancestors 'none'", "frame-ancestors 'self'");
// Routes that stream stored files and may be shown in a same-origin <iframe>.
const EMBEDDABLE_FILE_ROUTES = ["/files/:path*", "/imports/:id/file"];
const securityHeaders = [
{ key: "Content-Security-Policy", value: csp },
// HSTS bewusst zusätzlich in der App (neben dem Coolify-/Traefik-Proxy).
@@ -46,17 +53,27 @@ const securityHeaders = [
const nextConfig: NextConfig = {
// Standalone-Output für den Docker-Multi-Stage-Build (siehe Dockerfile)
output: "standalone",
experimental: {
// Proxy (src/proxy.ts) buffers request bodies; the 10 MB default truncates uploads silently.
// Largest allowed upload is 25 MB (PDF import) plus multipart overhead.
proxyClientMaxBodySize: "26mb",
},
// i18n-Kataloge werden zur Laufzeit per fs geladen (src/i18n/request.ts) — für den
// standalone-Output explizit mitkopieren.
outputFileTracingIncludes: {
"/*": ["./messages/**/*.json"],
},
async headers() {
// Later entries override same-named headers of earlier matches (Next.js header semantics).
return [
{
source: "/:path*",
headers: securityHeaders,
},
{ source: "/:path*", headers: securityHeaders },
...EMBEDDABLE_FILE_ROUTES.map((source) => ({
source,
headers: [
{ key: "Content-Security-Policy", value: cspEmbeddable },
{ key: "X-Frame-Options", value: "SAMEORIGIN" },
],
})),
];
},
};