kundenportal-demo · Konzept

Architektur: Mandanten und Demo-Pass#

Stand: 2026-09-30 · Beschreibt den Ist-Stand des Codes (Phase 4 abgeschlossen, Release v0.4.0, live geprüft am 30.09.2026; danach geschlossen: atomare Obergrenze, Upload-Kontingent, Einstellungen und Angebot im API, Hinweise an den Inhaber — noch nicht live geprüft). Ergänzt die Architektur, Zonen & Frontend und Altsysteme & Migration; Anforderungen und Grundentscheidung (Bridge-Modell) stehen in Demo-Pass. Kennzeichnung: [B] belegt, [A] Annahme, [E] Einschätzung.

Fachbegriffe sind in jedem Abschnitt beim ersten Vorkommen mit dem Glossar verlinkt (Erklärung und Entsprechung außerhalb von AWS).

Überblick#

Der Inhaber stellt Einladungslinks aus. Wer einen Link einlöst, erhält einen Demo-Pass (7 Tage, Kontingent) und damit einen eigenen Mandanten im Bridge-Modell: Die Lambdas, das API und der Ereignisbus sind geteilt; Daten, Altsystem- Datenstand, Uploads und Konten gehören dem Mandanten allein. Nach Ablauf baut das System den Mandanten vollständig zurück.

Gebaut ist das im Service services/tenancy (API, öffentliche Einlöse- Funktionen, Worker), in packages/service-kit (tenantData, TenantDirectory, Kontingent-Wächter im Router), in den Cognito-Triggern (services/identity), in beiden Altsystemen (Datenstand je Mandant) und in Shell und Cockpit (Zonen & Frontend §8).

≈ 10 s
vom Einlösen bis zum nutzbaren Mandanten (Ziel < 1 min)
≈ 10 s
Rückbau nach Ablauf
23/23
E2E-Schritte grün, inkl. J2/J3/J4/J6 im Pass-Mandanten
3 (bis 4)
gleichzeitige Pass-Mandanten höchstens, atomar gezählt
Lebenszyklus eines Mandanten
EinladungInhaber erzeugt im Cockpit einen Link für eine E-Mail-Adresse (einmalig, 14 Tage)EinlösenBesucher öffnet den Link, löst das ALTCHA-Rätsel; Pass wird ausgestelltEinrichtungTabelle, Altsystem-Datenstand, Konto des Pass-Inhabers, Ablauf-Zeitplan (gemessen ≈ 10 s)Tag 1–7Nutzung mit den Demo-Personen des Mandanten; Kontingent sichtbarTag 7Einmal-Zeitplan oder täglicher Abgleich (03:30) meldet den AblaufRückbauTabelle, Altsystem-Daten, Uploads, Konten, Zeitplan, Plattform-Einträge (gemessen ≈ 10 s)
Daten als Tabelle
DatumEreignis
EinladungInhaber erzeugt im Cockpit einen Link für eine E-Mail-Adresse (einmalig, 14 Tage)
EinlösenBesucher öffnet den Link, löst das ALTCHA-Rätsel; Pass wird ausgestellt
EinrichtungTabelle, Altsystem-Datenstand, Konto des Pass-Inhabers, Ablauf-Zeitplan (gemessen ≈ 10 s)
Tag 1–7Nutzung mit den Demo-Personen des Mandanten; Kontingent sichtbar
Tag 7Einmal-Zeitplan oder täglicher Abgleich (03:30) meldet den Ablauf
RückbauTabelle, Altsystem-Daten, Uploads, Konten, Zeitplan, Plattform-Einträge (gemessen ≈ 10 s)

1. Mandanten und Kennungen#

Mandant Kennung Daten Konten
Inhaber (Bestand) owner Tabelle der Base (unverändert) bisherige Konten, Gruppe owner
Demo-Pass p + 7 Zeichen Base32, z. B. p4k7x2qa eigene Tabelle kp-tenant-<kennung> Pass-Inhaber (Gruppe pass) und Demo-Personen des Mandanten

Die Kennung passt in alle bestehenden Muster (Token-Claim /^[a-z0-9-]{1,40}$/, Altsysteme /^[a-z0-9][a-z0-9-]{0,39}$/, Tabellennamen). Sie ist zufällig, damit niemand fremde Mandanten errät.

Entscheidung: Der Inhaber-Mandant bleibt in der Tabelle der Base. Eine Umzugs-Migration brächte Ausfallrisiko ohne Nutzen; die Schlüssel tragen ohnehin TENANT#owner#…. Die Plattform-Daten (Einladungen, Pässe, Mandanten, Kontingente) liegen ebenfalls dort, unter eigenen Präfixen (Fachkonzept §7.2):

PK SK Inhalt
INVITE#<sha256(token)> META E-Mail, erstellt, gültig bis (TTL 14 Tage), eingelöst
PASS#<passId> META Mandant, E-Mail, Status, ausgestellt, gültig bis; bleibt nach dem Rückbau 30 Tage als Nachweis, dann TTL
TENANT#<kennung> QUOTA#<art> Zähler used (api, events, uploads) — der Router und Documents kennen nur den Mandanten, nicht den Pass; uploads trägt zusätzlich exceededAt (Merker: QuotaExceeded einmal gemeldet)
PLATFORM TENANT#<kennung> Tabelle, Status (provisioning, active, quota-exceeded, tearing-down, deleted), Pass — Liste für Abgleich und Cockpit
PLATFORM SETTINGS Einlösen offen/gesperrt (Kill-Switch) mit closedAt/closedReason, Obergrenze maxTenants (Vorgabe 3, höchstens 4), Zähler activeTenants (Mandanten, die nicht deleted sind)
EMAIL#<sha256(adresse)> PASS ein Pass je E-Mail-Adresse
RATE#<sha256(ip)> REDEEM Einlöseversuche je IP, TTL 1 h
ALTCHA#<sha256(signatur)> USED gelöste Rätsel, Replay-Schutz, TTL bis zum Ablauf des Rätsels

2. Bindung von Konten an den Mandanten#

custom:tenant_id ist in Cognito unveränderlich und lässt sich nur beim Anlegen eines Kontos setzen. Genau das nutzt die Umsetzung — jedes Konto eines Pass-Mandanten entsteht durch das System, nie durch Selbstregistrierung:

Demo-Passwort je Mandant: Das Einrichten erzeugt ein zufälliges Passwort und übergibt es beiden Altsystemen, die damit den Datenstand des Mandanten anlegen (ausdrückliches PUT je Mandant; DELETE baut ihn zurück; unbekannte Mandanten außer owner beantworten die Altsysteme mit 404). Die Statusseite zeigt es dem Pass-Inhaber zusammen mit den Anmeldenamen der Demo-Personen. So verrät ein Pass nichts über den Inhaber-Mandanten. Die Telko-Anmeldung eines Pass-Mandanten prüft das Telko-Altsystem direkt (checkLogin, POST /v2/auth/check); der Keycloak-Realm telko bleibt dem Inhaber-Mandanten vorbehalten.

3. Isolation#

Zugriff einer geteilten Lambda auf Mandantendaten
MandantToken oder Ereignistenant_id aus dem geprüftenJWT bzw. detail.tenantIdToken Vendingsts:AssumeRoleRolle aus der Base,Sitzungs-Tag tenant, 15 min,gecacht je MandantClientDynamoDB, S3Clients mit diesenAnmeldedatenIAMRichtlinienurtable/kp-tenant-${aws:PrincipalTag/tenant}unduploads/${aws:PrincipalTag/tenant}/*
Daten als Tabelle
SchichtKomponente
MandantToken oder Ereignis (tenant_id aus dem geprüften JWT bzw. detail.tenantId)
Token Vendingsts:AssumeRole (Rolle aus der Base, Sitzungs-Tag tenant, 15 min, gecacht je Mandant)
ClientDynamoDB, S3 (Clients mit diesen Anmeldedaten)
IAMRichtlinie (nur table/kp-tenant-${aws:PrincipalTag/tenant} und uploads/${aws:PrincipalTag/tenant}/*)

4. Einlösen und Missbrauchsschutz#

5. Kontingente#

Größe Grenze Zählung
Laufzeit 7 Tage Zeitplan + täglicher Abgleich
API-Aufrufe 5.000 (QUOTA_API_CALLS) service-kit-Router vor jeder Route eines Pass-Mandanten: atomares ADD auf TENANT#<kennung>/QUOTA#api der Base mit Bedingung, an der Grenze 429; Mandant nicht active → 403 (quota-exceeded → 429), Status 30 s gecacht (TenantDirectory). Die Tenancy-Routen selbst umgehen den Wächter, damit Statusseite und Cockpit erreichbar bleiben
Domänen-Ereignisse 1.000 eine Regel „alle kundenportal.*-Ereignisse mit detail.tenantId Präfix p" an den Tenancy-Worker; der zählt QUOTA#events, das erste Ereignis über der Grenze setzt quota-exceeded → API 429
Uploads 20 (QUOTA_UPLOADS), je ≤ 5 MB, nur JPEG/PNG/PDF Documents vor jeder presignierten Upload-URL eines Pass-Mandanten: atomares ADD auf TENANT#<kennung>/QUOTA#uploads der Base mit Bedingung used < 20, an der Grenze 429 „Kontingent erschöpft" und einmal QuotaExceeded (Art uploads); die übrige Nutzung bleibt möglich. Größe (signierte Länge, Nachprüfung im Worker) und Typ prüft Documents wie bisher; der Inhaber zählt nie
Gleichzeitige Instanzen 1 je Pass ein Mandant je Pass
E-Mails 1 (Einmal-Passwort) keine weiteren E-Mails an Pass-Inhaber

Bei Überschreitung erscheint QuotaExceeded (Quelle kundenportal.tenancy, auch wenn Documents es für die Uploads veröffentlicht); die Shell zeigt „Kontingent: … übrig · gültig bis …" aus GET /api/tenancy/pass auf der Seite /pass (Komponente Meter aus packages/ui).

6. Kosten und Obergrenze#

Baustein Kosten Begründung
Tabelle je Mandant, provisioned 5/5 0 $ Always Free: 25 RCU/25 WCU je Konto und Region; Base 5/5 + 3 × 5/5 = 20, höchstens 5 + 4 × 5 = 25 [B]
Einmal-Zeitplan je Pass 0 $ EventBridge Scheduler, wird nach dem Auslösen gelöscht
STS AssumeRole 0 $ STS ist kostenlos [B]
EventBridge Scheduler (Zeitpläne und täglicher Abgleich) 0 $ 14 Mio. Aufrufe/Monat frei [B]
Cognito-Konten 0 $ 10.000 aktive Nutzer/Monat frei [B]
ALTCHA 0 $ eigene Lambda, kein Drittanbieter
CreateTable/DeleteTable 0 $ Steuerungsaufrufe sind kostenlos [B]

Daraus die Obergrenze von 3 gleichzeitigen Pass-Mandanten (Vorgabe in PLATFORM/SETTINGS, maxTenants). Der Inhaber kann sie über PUT /api/tenancy/settings auf 1 bis 4 setzen; 4 schöpft die freien 25 Einheiten genau aus, mehr lässt das API nicht zu. Mehr Mandanten trüge im Free Plan das Guthaben, danach kostete jede weitere Tabelle 5 × (0,00065 + 0,00013) $/h ≈ 2,85 $/Monat [A]. Gezählt wird atomar (Abschnitt 4).

7. Ablauf und Rückbau#

8. Ereignisse#

Ereignis Quelle Bedeutung
InvitationCreated kundenportal.tenancy Link erzeugt (ohne Token)
DemoPassIssued kundenportal.tenancy Link eingelöst, Mandant reserviert
TenantProvisioned kundenportal.tenancy Mandant nutzbar
QuotaExceeded kundenportal.tenancy eine Grenze erreicht
DemoPassExpired kundenportal.tenancy Laufzeit vorbei oder widerrufen
TenantDeleted kundenportal.tenancy Rückbau abgeschlossen

tenantId im Umschlag ist bei allen die Kennung des Pass-Mandanten. DemoPassIssued startet die Einrichtung, DemoPassExpired den Rückbau (beides im Tenancy-Worker).

Hinweise an den Inhaber: Der Tenancy-Worker schickt über das Inhaber-Thema (SNS, OWNER_TOPIC_ARN, dasselbe Thema wie die Hinweise des Notification-Service) je eine kurze Nachricht, wenn ein Mandant nutzbar ist („Demo-Pass eingelöst: , Mandant , gültig bis …") und wenn er gelöscht ist („Demo-Pass beendet (abgelaufen|widerrufen): , Mandant gelöscht, Konten, Uploads"). Beide gehen genau einmal hinaus (nur der Lauf, der den Status umstellt), nennen nie das Demo-Passwort oder einen Token, und ein Fehler beim Versand bricht Einrichtung oder Rückbau nicht ab.

9. Ausblick: Silo für den Inhaber#

Ein vollständiger eigener Stack je Instanz (Silo) zeigt Infrastruktur als Code am deutlichsten, dauert aber 2–5 Minuten und belegt je Instanz einen Bus und eine Distribution. Er bleibt eine Option für den Inhaber und ist nicht Teil von Phase 4.

10. Messwerte#

Gemessen am 30.09.2026 gegen die Live-Umgebung (Playwright, CloudWatch-Logs, curl).

Messgröße Wert Anmerkung
Einlösen → Mandant active ≈ 10 s davon Worker 9,6 s (Tabelle, Altsysteme, Konto, Zeitplan); Ziel < 1 min erreicht
Ablauf → Rückbau abgeschlossen ≈ 10 s kurzer Test-Pass, Zeitplan löst aus
ALTCHA lösen ≈ 0,6 s Node.js im E2E-Test; im Browser selbsttätig
E2E gesamt 23/23 grün inkl. J2, J3, J4, J6 im Pass-Mandanten, Isolation im Cockpit, Ablauf und Löschung, Anmeldung danach abgelehnt
Kill-Switch aktiv → Einlösen 503
wiederholtes ALTCHA 400 Replay-Schutz
unbekannter Link 404
Altsystem-Daten eines gelöschten Mandanten 404 beide Altsysteme

11. Befunde aus dem Live-Test#

Drei Fehler fielen erst live auf und sind behoben:

Offen#

Keine offenen Punkte. In v0.4.1 nachgezogen und live per E2E geprüft (25/25): atomare Obergrenze, Upload-Kontingent, Einstellungen im Cockpit, Angebotsdaten auf der Einlöseseite, Hinweise an den Inhaber, Aufräumen beim Demo-Reset, CDN-cachebare Startseite, Pausenseite, Version und Cockpit-Link in der Kopfzeile.