25. Security
Datenbankzugriff
Alle Server-Aktionen laufen über einen Service-Role-Client (prontoAdmin()), der Row
Level Security bewusst umgeht — Zugriffskontrolle erfolgt daher ausschließlich im
Anwendungscode (userId/kunde_id-Filterung in jeder Abfrage), nicht über RLS-Policies
auf Client-Ebene. Der Browser/Client hat nie einen direkten, ungeprüften Datenbankzugriff.
Tenant-Isolation
Jede Aktion — ob eingeloggt oder öffentlich (Buchungsseite, Formular, Newsletter) — ist
strikt an eine kunde_id/workspace gebunden. Öffentliche Buchungs-Endpunkte prüfen
zusätzlich explizit kalender.kunde_id === eingabe.kundeId, bevor ein Termin angelegt
wird, damit eine falsch übergebene Kalender-ID keinen fremden Kalender treffen kann.
Plan-Gating ist immer serverseitig
Jede planpflichtige Funktion (Automatisierungen, Newsletter, KI) wird über
requireFeature(userId, feature) serverseitig anhand der authentifizierten userId
geprüft — UI-seitiges Ausblenden (usePlan().canAccess(...)) ist ausschließlich UX,
niemals der eigentliche Schutzmechanismus (siehe 26-billing.md).
Geheimnisse (Tokens, Codes, Passwörter)
- Bestätigungscodes und Passwort-Reset-Token werden nie im Klartext gespeichert,
sondern als SHA-256-Hash (
email_verification_codes.code_hash,password_reset_tokens.token_hash) — beide zeitlich begrenzt (15 Minuten) und einmalig verwendbar. - OAuth-Zugangs-/Refresh-Token (Google, Zoom) sowie IMAP/SMTP-Zugangsdaten werden serverseitig gespeichert, nie an den Client ausgeliefert.
Spam-/Missbrauchsschutz
Siehe 24-rate-limits.md: Honeypot, Mindest-Ausfüllzeit, IP-Hash-Rate-Limit auf allen öffentlichen Formular-/Anmelde-Endpunkten. IP-Adressen werden nie im Klartext gespeichert, nur als SHA-256-Hash.
Admin-Zugriff
Admin-Rechte sind eine feste, serverseitig konfigurierte E-Mail-Allowlist
(PRONTO_ADMIN_EMAILS) — kein datenbankgespeichertes, client-manipulierbares Flag.
Webhook-Verifizierung
- Stripe-Webhook: Signaturprüfung (
verifyWebhook) gegen das Stripe-Secret. - WhatsApp-Webhook: Verifizierung über
WHATSAPP_VERIFY_TOKENbeim initialen Meta-Handshake. - Interne Cron-Callbacks (Erinnerungen, Follow-ups, System-E-Mails): geschützt durch ein
Shared Secret im Header
x-pronto-cron-secret, verglichen gegenFOLLOWUP_CRON_SECRET.
DSGVO
- Formular- und Newsletter-Einsendungen erfordern eine explizite, serverseitig erzwungene Datenschutz-Einwilligung.
- Double-Opt-in für Newsletter-Anmeldungen.
- Jede Newsletter-Mail enthält einen funktionierenden Abmeldelink.
Passwort-Reset — unabhängig von Supabase Auth Hooks
Bewusste Design-Entscheidung: Statt eines Supabase-Auth-Hooks (der in der Vergangenheit zu nicht erreichbaren Webhooks führte) nutzt PRONTO ein vollständig eigenständiges, von Supabase unabhängiges Passwort-Reset-System mit eigener Token-Tabelle — siehe 22-authentication.md.
