From eb616ddbe8b723283daff5818d7e8f8b7bd01e61 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 5 Oct 2026 09:07:48 +0000 Subject: [PATCH] Shop H2: Standalone-Seiten GET /shop/:siteKey und /shop/:siteKey/widerruf mit neutraler Seite, Schutz-Headern und Route-Test Co-Authored-By: Claude Opus 5.5 --- public/shop-seite.html | 23 +++++++++++ server/index.js | 3 ++ server/routes/shopSeite.js | 62 +++++++++++++++++++++++++++++ test/shopSeite.test.js | 81 ++++++++++++++++++++++++++++++++++++++ 4 files changed, 169 insertions(+) create mode 100644 public/shop-seite.html create mode 100644 server/routes/shopSeite.js create mode 100644 test/shopSeite.test.js diff --git a/public/shop-seite.html b/public/shop-seite.html new file mode 100644 index 0000000..203a116 --- /dev/null +++ b/public/shop-seite.html @@ -0,0 +1,23 @@ + + + + + + +Shop + + + +
Shop
+
+

Der Shop wird geladen …

+ +
+ + + diff --git a/server/index.js b/server/index.js index 3bbb0ae..3b82457 100644 --- a/server/index.js +++ b/server/index.js @@ -19,6 +19,7 @@ import funktionenRoutes from './routes/funktionen.js' import termineRoutes from './routes/termine.js' import shopRoutes from './routes/shop.js' import shopWebhookRoutes from './routes/shopWebhook.js' +import { shopSeiteRouter } from './routes/shopSeite.js' import takeoverCreditRoutes from './routes/takeoverCredits.js' import { purgeExpiredUploads } from './services/takeoverCredits.js' import { purgePendingChatUploads } from './services/chatAttachments.js' @@ -120,6 +121,8 @@ app.use('/embed/', (_req, res, next) => { }) // Adressen ohne Fassung: aktuelle Fassung der Rechtstexte (Analyse und Terminplaner; vor express.static) app.use('/rechtliches', legalRouter()) +// Webshop-Seiten fuer Kaeufer: /shop/:siteKey und /shop/:siteKey/widerruf (vor express.static) +app.use(shopSeiteRouter()) // Abgeschaltetes Admin-Panel und Rechnungsanalyse: Links aus alten Team-Mails ins Ticketsystem app.use('/admin.html', legacyAdminRedirect.admin) app.use('/kalkulation.html', legacyAdminRedirect.kalkulation) diff --git a/server/routes/shopSeite.js b/server/routes/shopSeite.js new file mode 100644 index 0000000..4503411 --- /dev/null +++ b/server/routes/shopSeite.js @@ -0,0 +1,62 @@ +import { readFileSync } from 'node:fs' +import path from 'node:path' +import { fileURLToPath } from 'node:url' +import { Router } from 'express' + +/** + * Oeffentliche Standalone-Seiten des Webshops (Plan 2026-10-05-webshop.md, H2; Spec 5.4): + * GET /shop/:siteKey Shop mit dem Widget public/embed/shop.js + * GET /shop/:siteKey/widerruf dieselbe Seite, das Widget oeffnet gleich die Widerrufsfunktion + * Die Seite verraet nicht, ob der Schluessel existiert oder der Shop aktiv ist: jeder syntaktisch gueltige Schluessel + * bekommt dieselbe Seite, ob der Shop laeuft entscheidet das Widget ueber die oeffentliche Schnittstelle. Alles andere ist 404. + */ + +const SITE_KEY_RE = /^[a-z2-7]{16}$/ +const DATEI = path.join(path.dirname(fileURLToPath(import.meta.url)), '..', '..', 'public', 'shop-seite.html') +const CSP = [ + "default-src 'self'", + "img-src 'self' data:", + "script-src 'self'", + "style-src 'self' 'unsafe-inline'", // das Widget legt ein