Terminplaner: oeffentliche Schnittstelle v1 mit Missbrauchsschutz

Router /api/public/termine/v1 (config, tage, zeiten, buchungen, widerruf, Token-Seiten /termin/*, Kalender-Abo) mit
eigenem 16-KB-Parser und Fehlermiddleware, CORS nach Herkunft, Honeypot und Formular-Token, Grenzen je IP (IPv6 je /64),
siteKey und Adresse in limits.js, Mindestantwortzeit (TERMINE_MIN_ANTWORT_MS) und 10-Sekunden-Cache fuer tage/zeiten.
Vor dem globalen express.json eingehaengt; /api/public/ ist vom Kundenansicht-Waechter ausgenommen.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
ClaudeandClaude Sonnet 5.5 committed 2026-10-03 08:17:43 +00:00
1 parent 43ac05aa06
commit 2b0c05b8e9
10 files changed
+1928 -7

No files matched your search

+9
View File
@@ -7,6 +7,7 @@ const TERMINE_ENV = [
'TERMINE_ENABLED',
'TERMINE_PILOT_EMAILS',
'TERMINE_FORM_MIN_SECONDS',
'TERMINE_MIN_ANTWORT_MS',
'APPWRITE_COLLECTION_TERMINE_CONFIG',
'APPWRITE_COLLECTION_TERMINE_BOOKINGS',
'APPWRITE_COLLECTION_TERMINE_CLAIMS',
@@ -47,6 +48,7 @@ test('Konfiguration: Vorgaben ohne Umgebungsvariablen', async () => {
zelleMin: 5,
mailsProStunde: 200,
formMinSekunden: 3,
minAntwortMs: 300,
})
assert.equal(fresh.collections.termineConfig, 'termineConfig')
assert.equal(fresh.collections.termineBookings, 'termineBookings')
@@ -59,6 +61,7 @@ test('Konfiguration: Umgebungsvariablen werden gelesen und bereinigt', async ()
TERMINE_ENABLED: ' Pilot ',
TERMINE_PILOT_EMAILS: ' Kenso.Gri@Gmail.com, ,justin@webklar.com ',
TERMINE_FORM_MIN_SECONDS: '0',
TERMINE_MIN_ANTWORT_MS: '0',
APPWRITE_COLLECTION_TERMINE_CONFIG: 'tConfig',
APPWRITE_COLLECTION_TERMINE_BOOKINGS: 'tBookings',
APPWRITE_COLLECTION_TERMINE_CLAIMS: 'tClaims',
@@ -67,6 +70,7 @@ test('Konfiguration: Umgebungsvariablen werden gelesen und bereinigt', async ()
assert.equal(fresh.termine.mode, 'pilot')
assert.deepEqual(fresh.termine.pilotEmails, ['kenso.gri@gmail.com', 'justin@webklar.com'])
assert.equal(fresh.termine.formMinSekunden, 0)
assert.equal(fresh.termine.minAntwortMs, 0)
assert.deepEqual(
[fresh.collections.termineConfig, fresh.collections.termineBookings, fresh.collections.termineClaims, fresh.collections.termineOutbox],
['tConfig', 'tBookings', 'tClaims', 'tOutbox'],
@@ -83,6 +87,11 @@ test('Konfiguration: leerer oder unbekannter Schalter ist off, ungueltige Sekund
assert.equal(fresh.termine.formMinSekunden, 3, `TERMINE_FORM_MIN_SECONDS=${JSON.stringify(value)}`)
}
assert.equal((await freshConfig({ TERMINE_FORM_MIN_SECONDS: '7' })).termine.formMinSekunden, 7)
for (const value of ['', 'abc', '-1', 'NaN']) {
const fresh = await freshConfig({ TERMINE_MIN_ANTWORT_MS: value })
assert.equal(fresh.termine.minAntwortMs, 300, `TERMINE_MIN_ANTWORT_MS=${JSON.stringify(value)}`)
}
assert.equal((await freshConfig({ TERMINE_MIN_ANTWORT_MS: '750' })).termine.minAntwortMs, 750)
})
test('termineModus: leer und unbekannt sind off, pilot und on bleiben', () => {