/* ===================================================================== index.js — Haupteinstiegspunkt für DogFather Universe. Liefert die statische Website aus (../ ist der Ordner mit allen .html-Dateien) plus die Zugangsschranke (gate.js), 1:1 wie vorher der Cloudflare Worker (gate-worker.js). ===================================================================== */ import "dotenv/config"; import express from "express"; import cookieParser from "cookie-parser"; import { dirname, join } from "node:path"; import { fileURLToPath } from "node:url"; import { gateMiddleware } from "./gate.js"; const __dirname = dirname(fileURLToPath(import.meta.url)); const SITE_DIR = join(__dirname, ".."); const PORT = Number(process.env.PORT || 4100); const app = express(); app.set("trust proxy", 1); app.disable("x-powered-by"); // verrät sonst unnötig den eingesetzten Technik-Stack /* Sicherheits-Header, ergänzt 05.08.2026. Vorher standen diese in der Datei `_headers` — die funktioniert aber NUR bei Cloudflare Pages/Netlify und wurde auf dem eigenen Server schlicht ignoriert, d.h. die Seite lief seit dem Umzug komplett ohne diese Schutzmaßnahmen (nachgeprüft an den Live-Antwortheadern). */ app.use((req, res, next) => { res.setHeader("X-Content-Type-Options", "nosniff"); res.setHeader("X-Frame-Options", "SAMEORIGIN"); res.setHeader("Referrer-Policy", "strict-origin-when-cross-origin"); res.setHeader("Permissions-Policy", "geolocation=(), microphone=(), camera=(), payment=()"); res.setHeader("Strict-Transport-Security", "max-age=31536000; includeSubDomains"); next(); }); app.use(cookieParser()); app.use(express.json({ limit: "1mb" })); app.use(gateMiddleware); /* Sicherheits-Fix 20.08.2026 (Audit vor dem geplanten öffentlichen Start morgen). SITE_DIR ist der GESAMTE Repo-Ordner (join(__dirname, "..")) — express.static liefert ohne diese Sperre daher nicht nur die eigentliche Website aus, sondern auch den Server- Quellcode (server/, server-internal/, cloudflare-worker/) und das komplette .git-Verzeichnis samt Commit-Historie. Live nachgewiesen (mit gültigem Zugangscode, der morgen für alle wegfällt): /server/gate.js und /.git/config lieferten HTTP 200. Ursache: serve-static blockt per Voreinstellung nur Dateien, deren EIGENER Name mit einem Punkt beginnt (server/.env -> 404 durch diese Regel) — aber NICHT rekursiv. Eine Datei wie .git/config wird trotzdem ausgeliefert, weil "config" selbst nicht mit einem Punkt beginnt, nur der Ordnername davor. Diese Sperre läuft daher bewusst unabhängig von gateMiddleware (bleibt auch nach dem morgigen Entfernen der Zugangsschranke wirksam) und blockt ganze Ordner/Muster statt einzelner Dateien, damit neue Dateien darin automatisch mitgeschützt sind. */ const GESPERRTE_ORDNER = ["/server", "/server-internal", "/cloudflare-worker"]; const GESPERRTE_DATEIEN = [ "/CLAUDE.md", "/DEPLOY.md", "/wrangler.toml", "/netlify.toml", "/gate-worker.js", "/_headers", "/.gitignore", "/.nojekyll", "/.assetsignore", ]; app.use((req, res, next) => { const pfad = req.path; const hatVerstecktenOrdner = pfad .split("/") .some((teil) => teil.startsWith(".") && teil.length > 1 && teil !== ".well-known"); const inGesperrtemOrdner = GESPERRTE_ORDNER.some((p) => pfad === p || pfad.startsWith(p + "/")); const istGesperrteDatei = GESPERRTE_DATEIEN.includes(pfad); const istBackupDatei = /\.bak(-|\.|$)/i.test(pfad); if (hatVerstecktenOrdner || inGesperrtemOrdner || istGesperrteDatei || istBackupDatei) { return res.status(404).sendFile(join(SITE_DIR, "404.html"), (err) => { if (err) res.status(404).send("Nicht gefunden."); }); } next(); }); app.use(express.static(SITE_DIR, { index: "index.html" })); app.use((req, res) => { res.status(404).sendFile(join(SITE_DIR, "404.html"), (err) => { if (err) res.status(404).send("Nicht gefunden."); }); }); /* Fehler-Handler (muss NACH allen Routen stehen), ergänzt 05.08.2026: antwortet mit einer schlichten Meldung statt einer Express-Standardseite mit komplettem Stacktrace und Dateipfaden. */ // eslint-disable-next-line no-unused-vars app.use((err, req, res, next) => { console.error(`[fehler] ${req.method} ${req.originalUrl}:`, err?.stack || err); if (res.headersSent) return; res.status(500).send("Es ist ein Fehler aufgetreten. Bitte später erneut versuchen."); }); /* Letztes Netz: Ein unbehandelter Fehler darf die Website nicht offline nehmen. */ process.on("unhandledRejection", (grund) => console.error("[unhandledRejection]", grund)); process.on("uncaughtException", (fehler) => console.error("[uncaughtException]", fehler?.stack || fehler)); app.listen(PORT, "127.0.0.1", () => { console.log(`DogFather Universe – Server läuft auf http://127.0.0.1:${PORT}`); });