Zum Hauptinhalt springen

Kunden-Self-Service-Portal (persistentes Kunden-Login) — Design (MED-D-279, MED-OP-PORTAL-1)

Kurz (Kernaussage zuerst): Ein persistentes, authentifiziertes Kundenportal, in dem die Kontaktperson eines Mandanten sich jederzeit einloggt, ihren eigenen Stand sieht („was ist offen / eingereicht / freigegeben?") und proaktiv Info-Aktualisierungen einreicht. Die Einreichung ist „submitted", nicht der tatsächliche Datenstand — sie wird zum Vorschlag und erst nach feldweiser Freigabe durch einen Medidentas-Berater (4-Augen-Prinzip) in die Stammdaten übernommen (versioniert, Quelle self_service). Das ist kein neuer Datenpfad: die komplette submitted-vs-actual-/4-Augen-/Übernahme- Maschinerie existiert bereits (domain/feld-sichtung.ts, …/uebernahme-vorschlag → …/uebernehmen, VersionedField). Neu ist allein eine persistente Identitäts- und Übersichts-Schicht darüber. Auth-Default: passwortlos / Magic-Link (+ optional Passkey) — kein Passwort-Hash at rest (G-5/G-6). Golden Rule & DSGVO: eine neue extern-authentifizierte Fläche → dokumentierte, begründete Zero-Trust-Ausnahme + DSGVO/AVV-Freigabe vor Go-Live (MED-KB-13, RISK-33).

True North: beantwortet #1 „Wo steht der Kunde?" (Kunde sieht/pflegt seinen Stand selbst, entlastet das Backoffice) und #3 „vollständig & rechtssicher dokumentiert?" (jede Einreichung/Freigabe append-only auditiert, 4-Augen erzwungen). Zielgruppen dieses Docs: IT-Dev/Architektur (Reuse-Map, Datenmodell, Slices) · CISO/Security (Auth, Zero-Trust, Angriffsfläche) · Business/Management (Nutzen, Compliance, Fahrplan).


1. Warum (Nutzen) & Abgrenzung​

FrageHeute (einladungsbasiert)Mit Portal (persistent)
Kunde aktualisiert Infosnur wenn der Berater einen Token-Link schickt (Einmal-Strecke)jederzeit selbst, ohne Anstoß durch den Berater
Kunde sieht seinen Standnein (nur das eine Formular hinter dem Link)Dashboard: offen / eingereicht / freigegeben
submitted ≠ tatsächlich✅ bereits so (Vorschlag → Übernahme)✅ unverändert (gleiche Maschine)
4-Augen-Freigabe✅ bereits so (feld-sichtung)✅ unverändert (gleiche Queue)
Identitätanonymer Token je Streckepersistentes Konto (E-Mail, an Mandant gebunden)

Abgrenzung: Das Portal ersetzt nicht die vier öffentlichen Kundenstrecken (Onboarding/Formular/ Fragebogen/Lead) — diese bleiben für den Erstkontakt/nicht-eingeloggten Fall. Das Portal ist die wiederkehrende, eingeloggte Sicht für bestehende Mandanten.


2. Reuse-Map (G-1 integrate-before-build · G-2 eine Wahrheit)​

Der Kern ist schon gebaut — das Portal ist eine dünne Schicht darüber. Nichts davon wird nachgebaut:

BausteinVorhanden alsPortal nutzt es für
submitted ≠ actualEinreichung → …/uebernahme-vorschlag (Vorschlag) ≠ StammdatenKunden-Einreichung landet als Vorschlag
4-Augen feldweisedomain/feld-sichtung.ts (akzeptiert/abgelehnt + Grund), …/feld-sichten, …/sichtung-abschliessenBerater-Freigabe je Feld
Korrektur-Schleifeabgelehnte Felder wieder geöffnet, akzeptierte gesperrt (MED-D-89)Kunde bessert nur Abgelehntes nach
Versionierte Übernahme + HerkunftVersionedField, StammdatenHerkunft.quelle (StammdatenQuelle-Enum), append-only Audit (G-4)Übernahme mit bestehender Quelle self_service (kein neuer Enum-Wert; das Portal ist derselbe „Kunde über den Aktualisierungs-Link"-Pfad)
Unguessbare Tokens + AblaufAusfuellformular.token/ablaufAm, Onboarding-EinladungMagic-Link = Token mit TTL (dieselbe Machart)
Bot-Schutzauth/turnstile.ts (Cloudflare Turnstile)Login-/Anforderungs-Formular gegen Bots
Scope-Guard „nur eigenes"ladeDokScope (fremder Mandant → 404, kein Existenz-Leak)Kunde sieht/ändert nur den eigenen Mandanten
Mandant-KontaktMandant.email (R1-F12), KontaktpersonenKonto-Bindung (E-Mail = Identität)

Bewusst NEU (die eigentliche Arbeit): persistente Kunden-Identität + Session + Dashboard + proaktiver Einreichungs-Einstieg. Alles andere = Verdrahtung an Bestehendes.


3. Datenmodell (neu, minimal — G-6 Datensparsamkeit)​

  • portal_konto — persistente Kunden-Identität. Felder: id · mandant_id (FK, Scope) · email (Identität; kein Passwort-Hash) · status (eingeladen/aktiv/gesperrt) · erstellt_am · letzter_login_am. Kein zusätzliches PII über die ohnehin vorhandene Kontakt-E-Mail hinaus (G-6).
  • portal_anmelde_token — Magic-Link: token_hash (Digest/HMAC des Tokens, nie der Rohwert — der Klartext-Token steht nur im Link, at rest liegt nur der Hash; ein DB-Leak ist damit nicht replaybar) · konto_id · ablauf_am (kurze TTL, z. B. 15 min) · verbraucht_am (Einmalgebrauch). Analog Ausfuellformular.token in der Ausgabe, aber gehasht gespeichert (Bearer-Credential).
  • portal_session — nach erfolgreichem Magic-Link: session_id_hash (Digest des Session-Cookies, nicht der Rohwert) · konto_id · erstellt_am · letzte_aktivitaet_am · zwei getrennte Ablauf- Grenzen: Idle-TTL (relativ zu letzte_aktivitaet_am, z. B. 30 min) und Absolut-TTL (relativ zu erstellt_am, z. B. 12 h) — beide werden bei jeder Anfrage geprüft · widerrufen_am (Logout/Sperre). Server-seitig, kein JWT-at-rest nötig.
  • Optional Passkey (WebAuthn) — späterer Slice: portal_passkey (credential_id, public_key) für wiederkehrende Logins ohne Mail-Runde. Kein Secret at rest (nur Public Key).

Derived, nie gespeichert (G-2): „was ist offen/eingereicht/freigegeben" wird abgeleitet aus den bestehenden Dokument-/Einreichungs-/Sichtungs-Zuständen — kein neuer Statusspeicher.


OptionPortal-ErlebnisSicherheit / ComplianceEmpfehlung
Passwortlos / Magic-LinkKonto, „jederzeit per Mail-Link rein" (+ optional Passkey)kein Passwort-Hash at rest, kleine Angriffsfläche, DSGVO-arm, nutzt bestehende Token-Infra✅ Default
Passwort-Loginklassisch vertrautCredential-at-rest (Hash), Reset-Flows, Breach-/Reuse-Risiko, mehr Härtungbegründungspflichtig
OIDC (Kunde bringt Google/MS)kein Medidentas-Secretexterne IdP-Abhängigkeit, für KMU-Kontakte oft unpassendNische

Härtung (verbindlich, alle Optionen): Rate-Limiting je IP+Konto, keine Konto-Enumeration (immer „falls ein Konto existiert, wurde eine Mail gesendet"), Turnstile am Login, Magic-Link Einmalgebrauch + kurze TTL, Session-Cookie HttpOnly/Secure/SameSite, absolute + Idle-Expiry, Logout=Session-Widerruf. Versand-Nachweis im Audit (MED-D-678): Jede Anforderung schreibt portal.anmeldung_angefordert mit zustellung (ok/fehler/kein_kanal) und kanal (Adapter-Name). Der Kanal ist nötig, weil der Log-Kanal nie wirft und damit ebenfalls ok erzeugt — erst kanal: "cloudflare-email" statt "log" belegt einen echten Versand (OP-DM-8-Gate). PII-arm: nur Status-Enum und Adapter-Name, kein Empfänger, kein Link-Inhalt. Gilt gleichermaßen für den Staff-Magic-Link (staff.anmeldung_angefordert). Ausgeweitet (MED-D-730) auf alle Mail-Sendepfade: kanal (Adapter-Name, PII-arm) steht jetzt auch in formular.erinnert, unterlagen_erinnerung.gesendet, dexman.monatsmeldung.abgeschlossen und wiedervorlage.zugewiesen (dort nur, wenn eine Mail versucht wird, zusätzlich mit zustellung); formular.gesichtet (feedbackKanal), fragebogen.entwurf_abgelaufen und wiedervorlage.erinnert hatten ihn schon; fragebogen.entwurf_abgelaufen trägt zusätzlich zustellung, ein werfender Adapter bricht den Cron-Lauf nicht mehr ab.

Erneut senden (MED-D-674): Die "Postfach prüfen"-Seite bietet "E-Mail erneut senden" — dieselbe Anfrage an POST /oeffentlich/portal/anmelden (kein zweiter Endpunkt, G-1/G-2), damit eine verlorene oder im Spam gelandete Mail keine Sackgasse ist. Bestätigung und Fehlertexte bleiben enumeration-frei ("falls ein Konto existiert …"); serverseitig gilt unverändert 5 Anfragen je Adresse / 10 min (plus 20 je IP), die Oberfläche legt zusätzlich 60 s Wartezeit zwischen zwei Klicks, damit niemand dieses Kontingent versehentlich aufbraucht. Das Turnstile-Widget wird dafür explizit gerendert (rendereTurnstile), weil Cloudflares Auto-Rendering nur beim Script-Load greift und ein nach einem Ansichtswechsel eingefügter Container sonst leer bliebe. Mail-Versand läuft über den Cloudflare Email Service (OP-DM-8 gebaut, MED-D-314 — plattform-natives send_email-Binding, löst den MailChannels-Adapter ab); ohne das EMAIL-Binding fällt er deploy-sicher auf den Log-Kanal zurück (keine Mail). Das Portal ist seit MED-D-310 scharfgeschaltet (PORTAL_AKTIV="true"); öffentlich erreichbar wird /portal erst mit dem Cloudflare-Access-Bypass (Deploy.md §7e).


5. RBAC & Isolation (strikt getrennt von Staff)​

  • Zwei getrennte Prinzipale: Staff = Akteur über Cloudflare Access (unverändert, Staff-only) · Kunde = portal_konto über eigene Session-Cookies (getrennter Realm, andere Middleware).
  • Kunde sieht ausschließlich den eigenen Mandanten — jeder Portal-Endpunkt trägt den ladeDokScope- artigen Guard (fremder Mandant → 404, kein Existenz-Leak). Ein Portal-Konto kann nie eine Staff-Route erreichen und umgekehrt.
  • Vorschlagend, nicht ausführend: der Kunde kann nur einreichen (Vorschlag). Die Übernahme in die Stammdaten bleibt ausschließlich beim Berater (4-Augen) — der Kunde hat keinen Schreibzugriff auf den tatsächlichen Datenstand.

6. Compliance — proaktiv geflaggt (verbindlich, DE/AT/CH)​

  • Golden Rule „Zero Trust vor Public" (RISK-33): Das Portal ist eine neue öffentlich-authentifizierte Fläche. Es wird als bewusste, begründete Ausnahme dokumentiert (wie die vier Kundenstrecken) — mit Rate-Limiting, No-Enumeration, Turnstile, Token-TTL, Session-Härtung. Nicht offen als Default; Go-Live erst nach Sign-off.
  • DSGVO/revDSG (MED-KB-13): persistente Kundenkonten = neue Verarbeitung → Rechtsgrundlage (Art. 6(1)(b) Vertragserfüllung), Betroffenenrechte (Auskunft/Berichtigung/Löschung/Übertragbarkeit), AVV mit Mail-/Auth-Provider. EU-/CH-Residenz ist vor Go-Live nachzuweisen (noch keine belegte Kontrolle): server/wrangler.toml trägt nur die D1-ID, --location weur steht bisher nur in Kommentar/ Runbook — Worker-Placement, D1-Region, Cloudflare-Account-Einstellungen und die realen Mail-/Auth-Provider- Bedingungen sind als Deployment-Evidenz zu belegen (S5-Gate). Medidentas = Verantwortlicher.
  • G-5/G-6 Datensparsamkeit: Konto = E-Mail + Mandant-Link, kein zusätzliches PII; Magic-Link vermeidet Secret-at-rest; Login-/Session-Events PII-arm auditiert (nur IDs/Enums).
  • G-4 Audit: Portal-Login, Einreichung, Berater-Freigabe/Ablehnung append-only auditiert (Actor = Portal-Konto bzw. Berater).
  • Aufbewahrung: Konto-/Session-Daten mit definierter Retention; Session-Widerruf bei Mandant-Archivierung.

7. Slice-Plan (MED-OP-PORTAL-1)​

SliceInhaltRisiko / Gate
S0 — Design (dieser PR)dieses Doc + Decision-Log (MED-D-279) + Risikoregister (RISK-33) + Kunden-Besprechungspunkt (MED-KB-13) + Lastenheft/Feature-Liste-Stubkeins (Doku)
S1 — FundamentDatenmodell portal_konto/portal_anmelde_token/portal_session (Token/Session-ID nur als Hash at rest; Session mit Idle- + Absolut-TTL) + Migration + Repo + Domänen-Kern (Token-Ausgabe/-Prüfung + atomarer Claim wie MED-OP-TIME-5, Session — reine Funktionen) + Vitest. Dormant, keine public Route.niedrig (nichts exponiert)
S2 — Auth ✅ gebaut (0.125.0, MED-D-283)Passwortlose Magic-Link-Routen (No-Enumeration, atomarer Einmal-Claim, HttpOnly/Secure/SameSite-Cookie mit Idle+Absolut-TTL) + Staff-Route „Portal-Login anlegen". Endpunkte: POST /oeffentlich/portal/anmelden · GET /oeffentlich/portal/login?token= · GET /oeffentlich/portal/ich · POST /oeffentlich/portal/abmelden · POST /api/mandanten/:id/portal-konto (Staff). Flag-gated: dormant, bis PORTAL_AKTIV=true; PORTAL_TEST_MODE=true gibt den Magic-Link in der Antwort zurück (Test/Demo, solange kein echter Mailversand OP-DM-8). Auth-Mechanismus per Nutzer bestätigt (MED-KB-13 (a): passwortlos, zum Testen). +9 Vitest (inkl. CodeRabbit-#320-Härtung: Login-Atomarität, Sperr-Check, istEmail).Gate erst bei S5-Aktivierung: DSGVO/AVV + Rate-Limiting/Turnstile (RISK-33) + realer Mailversand (OP-DM-8)
S3 — Dashboard ✅ gebaut (0.132.0, MED-D-304)Read-only „offen/eingereicht/freigegeben" je eigenem Mandanten (rein abgeleitet G-2): PortalService.dashboard + GET /oeffentlich/portal/dashboard, flag-gated (dormant → 404, 401 ohne Session).niedrig (nichts geschrieben)
S4 — Proaktive Einreichung ✅ gebaut (0.132.0, MED-D-304)Kunde schlägt Stammdaten-Updates vor (POST /oeffentlich/portal/stammdaten-vorschlag, whitelisted G-6) → kein Stammdatum geschrieben: Einreichung als self_service-FormularVorgang (status eingereicht) durch dieselbe 4-Augen-feld-sichtung/uebernahme (typ-bewusstes Regelset, Quelle self_service); Berater-Sicht GET /api/mandanten/:id/selbstauskuenfte + Sektion in mandant-formulare.mittel (schreibt Vorschlag, nicht Stammdaten)
S5-UI — Client-UI ✅ gebaut (dormant) (0.135.0, MED-D-308)Die Kundenoberfläche zu S1–S4: öffentliche Route /portal (portal-public.component.ts) — passwortloses Login (Magic-Link) + read-only Dashboard (S3) + proaktive Stammdaten-Einreichung (S4). Dormant hinter PORTAL_AKTIV (alle Endpunkte → 404 → „nicht verfügbar"). Reine Client-Anbindung an die bestehenden Endpunkte.niedrig (nichts exponiert, dormant)
S5 — Go-Live 🟡→✅ Code scharf (0.137.0/0.138.0, MED-D-310/311)MED-KB-13 (DSGVO/AVV) erteilt → PORTAL_AKTIV="true"; echter Mailversand gebaut (OP-DM-8, Cloudflare Email Service, MED-D-314 — dormant ohne EMAIL-Binding); Magic-Link-Bug behoben (Link → Client-Route /portal?token= statt API-JSON); Staff-„Portal-Zugang einrichten" im Kunden-Detail. Login-Härtung ✅ gebaut (MED-D-311, MED-OP-PORTAL-3): Turnstile + Rate-Limiting am anmelden. Deploy-Restschritte (Deploy.md §7e): Absender-Domain im Cloudflare Email Service onboarden · TURNSTILE_SITEKEY/TURNSTILE_SECRET · EU-/CH-Residenz-/Provider-Nachweis (AVV, §6) · Cloudflare-Access-Bypass für /portal. optional Passkey später.Gate Kundenfreigabe ✅, Login-Härtung ✅; verbleibend: Infra (Secrets/DNS/Access-Bypass) + Residenz-/AVV-Nachweis (§6)

Reihenfolge-Logik: S1 ist komplett ungefährlich (nur Datenmodell + pure Funktionen, dormant) → sofort baubar. Die öffentlich exponierten Slices (S2/S5) hängen an MED-KB-13 (Auth-Mechanismus + DSGVO/AVV- Freigabe durch den Kunden) und am realen Mail-Versand (OP-DM-8).


8. Offene Entscheidungen​

  • MED-KB-13 (Kundenfreigabe) ✅ erteilt (2026-09-23, MED-D-310): (a) Auth-Mechanismus passwortlos/ Magic-Link ✅, (b) DSGVO-Rechtsgrundlage ✅ + AVV/EU-CH-Residenz-Nachweis mit dem Mail-Provider den Cloudflare Email Service bleibt vor dem Access-Bypass zu belegen (§6), (c) mehrere Kontaktpersonen je Mandant → MED-OP-PORTAL-2 Teil 2 ✅ gebaut (MED-D-320) — mehrere Kontakt-Logins je Mandant, Staff-verwaltet, (d) proaktiv aktualisierbare Felder ✅ (S4-Whitelist).
  • MED-OP-PORTAL-2 (Portal löst die Einmal-Links vollständig ab): Teil 1 (Dokument-Upload) ✅ gebaut (MED-D-316) — der eingeloggte Kunde lädt seine angeforderten Dokumente direkt im Portal hoch (POST /oeffentlich/portal/dokumente/:docId/upload, Scope-Guard eigener Mandant, pruefeUpload + bestehende DokumentService.hochladen). Teil 2 ✅ gebaut (MED-D-320): mehrere Kontakt-Logins je Mandant — Staff legt je Mandant mehrere Portal-Kontakte an (anlegen/auflisten/sperren/reaktivieren, mandant-einladungen), jeder mit eigenem Magic-Link-Login, alle auf denselben Mandanten scoped; Sperre beendet laufende Sessions; Staff-gated (Zero-Trust), Autorisierung je Kontakt beim Staff (RISK-36). MED-OP-PORTAL-2 damit abgeschlossen.
  • OP-DM-8 (Mail-Versand) ✅ gebaut (MED-D-314): Cloudflare Email Service (send_email-Binding); live braucht er nur noch das Onboarding der Absender-Domain im Email Service.
  • Dexman-Monatsmeldung (MED-D-700) ✅: bei laufendem Dexman-Mandat steht "Umsätze melden" in der To-do-Liste; die Eingabe klappt in der Liste auf (Routen GET/POST /oeffentlich/portal/dexman/:praxisId/:monat, Session + Scope eigener Kunde). Übernahme erst nach Feld-Sichtung durch den Berater (MED-D-89), Quelle portal. Erweitert (Issue #790, MED-D-790): freiwillig meldet die Praxis zusätzlich die ausgezahlte Umsatzbeteiligung je Person (laufender Lohn / Einmalzahlung) und die Kassen- und Privatleistungen des Monats; jedes Feld wird einzeln gesichtet (Feldschlüssel beteiligung.<personId> und leistung), angenommene Werte gehen mit Quelle portal in dieselben Fakten wie die Berater-Erfassung.
  • Eine To-do-Liste (MED-D-333, MED-OP-UX-15 Slice B) ✅: das Dashboard zeigt "Was Sie noch tun müssen" — abgeleitet je Abruf (PortalDashboard.aufgaben, nichts persistiert, G-2) aus offener Onboarding-Einladung, Ausfüll-Formularen im Entwurf/zur Korrektur, Fragebogen-Einladungen und angeforderten Dokumenten; je Aufgabe ein Knopf auf die bestehende Token-Strecke bzw. Upload direkt in der Liste (keine neue öffentliche Route). Interne Checklisten-Items sind für den Kunden unsichtbar. Keep-alive: Tippen/Klicken hält die Sitzung über /oeffentlich/portal/ich aktiv (höchstens alle 5 Min., Timeout unverändert). Einladungs-/Anmelde-Mail erklären Zweck, Gültigkeit (15 Min., einmalig) und den Weg zu einem neuen Link.
  • Passkey (WebAuthn): optionaler Zusatz-Faktor für wiederkehrende Logins — nach S3.

Kunden-Erinnerung an fehlende Unterlagen (MED-D-638, 2026-09-27): Der stündliche Cron schickt je Kunde alle 7 Tage eine Sammel-Mail mit allen offenen Basis-Unterlagen/-Angaben und angeforderten bzw. abgelaufenen Dokumenten (inkl. Mandats-Unterlagen) und dem Portal-Link — höchstens 3 Mails je Zyklus, 7 Tage nach der dritten eine Wiedervorlage an den Betreuer (einmal je Zyklus). Der Zyklus beginnt, sobald der Cron offene Unterlagen sieht (erste Mail nach 7 Tagen), und endet, wenn nichts mehr fehlt (Zähler zurück). Standardmäßig für alle Kunden an; seit MED-D-654 kein Schalter mehr im Kunden-Detail (zu viel UI), abschaltbar nur über PUT /api/mandanten/:id/unterlagen-erinnerung; der Stand erscheint leise in der Kopfzeile der Basis-Dokumente ("2 von 3 Erinnerungen gesendet" bzw. "ausgeschaltet"). Nur für Kunden mit Kontakt-E-Mail, nicht für lead/archiviert; dormant, solange PORTAL_AKTIV nicht true ist oder kein Mail-Kanal besteht. Zustand in unterlagen_erinnerung (Migration v67, nur Zähler/Zeitpunkte); Mail enthält nur Unterlagen-Bezeichnungen und den Link, das Audit nur Zahlen (G-6).

Stand (MED-D-310, 2026-09-23): S1–S5 sind gebaut und der Code ist scharfgeschaltet — MED-KB-13 (DSGVO/AVV) vom Kunden erteilt → PORTAL_AKTIV="true", echter Mailversand gebaut (OP-DM-8, Cloudflare Email Service, MED-D-314 — dormant ohne EMAIL-Binding), Magic-Link zeigt auf die Client-Route. Der Portal-Zugang entsteht automatisch beim Kunden-Anlegen bei bekannter Kontakt-E-Mail (MED-D-315: Konto + Einladungs-Magic-Link automatisch, idempotent/nicht-fatal; der manuelle Staff-Weg im Kunden-Detail bleibt Fallback für abweichende E-Mails). Das Portal löst die Einmal-„Aktualisierung anfordern"-Links als Stammdaten-Self-Service ab; der Dokument-Upload läuft ab MED-D-316 ebenfalls im Portal (angemeldeter Kunde lädt angeforderte Dokumente selbst hoch, MED-OP-PORTAL-2 Teil 1 ✅). Ab MED-D-637 gibt es keinen manuell erzeugten Einmal-Link mehr: die Erstonboarding-Einladung (Einwilligung + Start-Angaben) entsteht automatisch, sobald der Kunde im Portal ist und das Onboarding läuft (idempotent, solange keine Einladung abgeschlossen ist); der Abschnitt "Dokumente & Onboarding anfordern" im Kunden-Detail entfällt, die Übernahme eingereichter Angaben bleibt. MED-OP-PORTAL-2 Teil 2 ✅ gebaut (MED-D-320): mehrere Kontakt-Logins je Mandant (Staff-verwaltet) → MED-OP-PORTAL-2 abgeschlossen. Login-Härtung ✅ gebaut (MED-D-311, MED-OP-PORTAL-3): Turnstile + fensterbasiertes Rate-Limiting am anmelden. Verbleibend (Infra/Nachweis, kein Code, Deploy.md §7e): Absender-Domain im Cloudflare Email Service onboarden · TURNSTILE_SITEKEY/TURNSTILE_SECRET setzen · EU-/CH-Residenz-/Provider-Nachweis (AVV) gemäß §6 (die MED-KB-13-Kundenfreigabe belegt nicht die Residenz) · dann Cloudflare-Access-Bypass für /portal. Bis der Access-Bypass gesetzt ist, liegt das Portal weiter hinter Access.