Merge remote-tracking branch 'origin/main' into feature/oeffnungszeiten
Zusammenführung von TP2-Abschluss und Webshop (origin/main cab9c33) mit Öffnungszeiten live. Beide
Funktionen bleiben vollständig: jeder Konflikt ist als Vereinigung gelöst, Reihenfolge der Funktionsschlüssel
überall news, termine, shop, zeiten.
Konfliktdateien (28): public/app.js, dashboard.html, funktionen.js, widerrufen.html; scripts/stripe-products.mjs;
server/config.js, index.js, middleware/kundenansichtGuard.js, routes/abo.js, routes/admin/funktionenAdmin.js,
routes/funktionen.js, services/customerAdmin.js, customerViewLog.js, featureRegistry.js, retention.js,
siteFeatures.js, stripeBilling.js; test/customerViewLog, featureFreischaltung, featureRegistry, funktionenRoutes,
funktionenTermine, kundenansichtGuard, mailTemplates, publicForms, siteFeatures, stripeProductsScript (je
.test.js) und test/support/schema.js.
Entscheidungen: Mailvorlagen G01–G15, K01–K30, T01–T21 lückenlos (K24–K26 Webshop, K27–K30 Öffnungszeiten);
Stripe-Katalog mit shopSetup und zeitenSetup; Widerruf kennt shop und zeiten; ABO_FUNKTIONEN bleibt analyse
und termine (kein zeiten); activate() schreibt die Zeiten-Felder nur über extra; kein Halter von
feature-activate:<id> ruft disable() auf; die Aufbewahrungs-Löschung löscht Zeiten-Daten vor dem Abschalten und
hält den Kunden bei einem Fehler (held); die Admin-Antwort trägt ersatzTicket und warnung; Hash-Routen webshop
und zeiten, Ansichten view-webshop und view-zeiten; assertOwnProject behält die Meldung von main.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
commit
e87140e5ba
245 files changed
+38799
-1050
No files matched your search
@@ -1,6 +1,20 @@
|
||||
import { config } from '../config.js'
|
||||
import { ADMIN_LABEL, STAFF_LABELS } from '../services/staffLabels.js'
|
||||
|
||||
export async function requireAppwriteStaff(req, res, next) {
|
||||
/**
|
||||
* Prueft das Appwrite-JWT aus dem Ticketsystem und verlangt eines der Labels `allowed`.
|
||||
* requireAppwriteStaff: nur "admin" (Kunden, Mitarbeiter, Finanzen ...).
|
||||
* requireAppwriteTeam: jeder Mitarbeiter ("mitarbeiter" oder "admin") - Projekt-Aktionen
|
||||
* (Anlegen, Zuordnen, Archivieren, Preview, Analyse, Gitea-Import), Entscheidung 2026-10-04.
|
||||
*/
|
||||
function requireAppwriteLabels(allowed, deniedMessage) {
|
||||
return (req, res, next) => checkAppwriteJwt(req, res, next, allowed, deniedMessage)
|
||||
}
|
||||
|
||||
export const requireAppwriteStaff = requireAppwriteLabels([ADMIN_LABEL], 'Admin-Berechtigung erforderlich')
|
||||
export const requireAppwriteTeam = requireAppwriteLabels(STAFF_LABELS, 'Nur für Mitarbeiter')
|
||||
|
||||
async function checkAppwriteJwt(req, res, next, allowed, deniedMessage) {
|
||||
const authHeader = req.get('Authorization') || ''
|
||||
const match = authHeader.match(/^Bearer\s+(.+)$/i)
|
||||
if (!match) {
|
||||
@@ -29,11 +43,11 @@ export async function requireAppwriteStaff(req, res, next) {
|
||||
}
|
||||
|
||||
if (!response.ok) {
|
||||
return res.status(401).json({ error: data?.message || 'Ung�ltiges JWT' })
|
||||
return res.status(401).json({ error: data?.message || 'Ungültiges JWT' })
|
||||
}
|
||||
|
||||
if (!Array.isArray(data?.labels) || !data.labels.includes('admin')) {
|
||||
return res.status(403).json({ error: 'Admin-Berechtigung erforderlich' })
|
||||
if (!Array.isArray(data?.labels) || !allowed.some((label) => data.labels.includes(label))) {
|
||||
return res.status(403).json({ error: deniedMessage })
|
||||
}
|
||||
|
||||
req.appwriteUser = data
|
||||
|
||||
@@ -7,7 +7,7 @@ export function corsMiddleware(allowedOrigins) {
|
||||
if (origin && allowedOrigins.includes(origin)) {
|
||||
res.setHeader('Access-Control-Allow-Origin', origin)
|
||||
res.setHeader('Access-Control-Allow-Headers', ALLOW_HEADERS)
|
||||
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PATCH, DELETE, OPTIONS')
|
||||
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, PATCH, DELETE, OPTIONS')
|
||||
if (req.method === 'OPTIONS') {
|
||||
return res.sendStatus(204)
|
||||
}
|
||||
|
||||
@@ -24,6 +24,16 @@ export const KUNDENANSICHT_ALLOWED = [
|
||||
['POST', /^\/api\/termine\/buchungen\/[^/]+\/aktion\/?$/],
|
||||
['PATCH', /^\/api\/termine\/einstellungen\/?$/],
|
||||
['DELETE', /^\/api\/termine\/buchungen\/[^/]+\/?$/],
|
||||
// Webshop: Artikel, Einstellungen, Rechtstexte-Entwurf und Bilder darf das Team mit dem Kunden ausfuellen (protokolliert). Genau diese
|
||||
// sieben Pfade, jeder einzeln: Anfrage, Oeffnen, Bestaetigen der Rechtstexte (der Haendler bestaetigt persoenlich), Stripe und
|
||||
// Erstattungen bleiben gesperrt, ohne dass es einen Eintrag braucht.
|
||||
['POST', /^\/api\/shop\/produkte\/?$/],
|
||||
['POST', /^\/api\/shop\/produkte\/sortierung\/?$/],
|
||||
['PUT', /^\/api\/shop\/produkte\/[^/]+\/?$/],
|
||||
['DELETE', /^\/api\/shop\/produkte\/[^/]+\/?$/],
|
||||
['PUT', /^\/api\/shop\/einstellungen\/?$/],
|
||||
['PUT', /^\/api\/shop\/rechtstexte\/?$/],
|
||||
['POST', /^\/api\/shop\/bild\/?$/],
|
||||
// Oeffnungszeiten: Speichern im Namen des Kunden (Spec 2026-10-04, Abschnitt 3.4); den WhatsApp-Haken sperrt die Route selbst (403).
|
||||
// Nur dieser eine Pfad: die Vorschau (GET) braucht keinen Eintrag, alles andere unter /api/zeiten ist gesperrt.
|
||||
['PUT', /^\/api\/zeiten\/[^/]+\/?$/],
|
||||
|
||||
Reference in new issue
Block a user