shinkan-jinkendo/.claude/docs/technical/MULTI_TENANCY_RBAC_ARCHITECTURE.md
Lars 0c044249d9
Some checks failed
Deploy Development / deploy (push) Successful in 37s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 6s
Test Suite / playwright-tests (push) Failing after 3m38s
Phase 0a Mandantenfähigkeit
2026-05-05 16:12:17 +02:00

12 KiB
Raw Blame History

Multi-Tenancy, Vereins-Membership und Rollenmodell Zielarchitektur & Umsetzungsplan

Status: verbindliche Zielrichtung (Architekturpapier)
Stand: 2026-05-05
Bezug: functional/shinkan_anforderungsdokument_entwurf.md §5, §1718 · functional/DOMAIN_MODEL.md (Sichtbarkeit §5.5) · functional/TRAINING_CURRICULUM_AND_GOVERNANCE_CONCEPT.md (CURR-004008)


1. Zweck

Dieses Dokument fasst den Soll-Zustand für Mandantenfähigkeit (Verein = Mandant), rollenbasierte Zugriffskontrolle auf Vereinsebene und ein Membership-/Limitsystem zusammen und definiert einen phasenweisen Umsetzungsplan, der mit dem bestehenden Governance-Kern (visibility, club_id, created_by) konsistent bleibt.


2. Abgleich mit vorhandener Dokumentation

Quelle Inhalt relevant für Tenancy/Rollen Konsistenz mit Zielbild
shinkan_anforderungsdokument_entwurf.md §5.4 Rollen: Superadmin, Vereinsadmin, Trainer, Co-Trainer, Redakteur Deckt sich; „Superadmin“ entspricht fachlich Systemadmin
§17.1 Erweiterung: Systemadmin, Spartenadmin Entspricht den gewünschten Spartenverantwortlichen
§5.5 / §17 Sichtbarkeit: privat, Verein, Sparte, global, offiziell DOMAIN_MODEL listet ähnliche Stufen; technische Durchsetzung ist noch lückenhaft
§18.5 MVP: Datenmodell mandantenfähig, Rechte zunächst einfach Bestätigt schrittweise Verschärfung
DOMAIN_MODEL.md §5.5 Freigabeebenen inkl. Sparte Zielbild; DB/API nutzen derzeit überwiegend private | club | official
TRAINING_CURRICULUM_AND_GOVERNANCE_CONCEPT.md CURR-004008: Governance-Kern, spätere Policy Datenfelder vorbereitet; Policy/Erzwingung folgt
working/SHINKAN_PROJECT_SETUP.md §6 „Multi-Tenant-Administration“ ausgeschlossen (MVP-Liste) Historisch; technische Mandanten sind dennoch Ziel UI-Komplexität kontrolliert einführen

Fazit: Die fachlichen Rollen und Sichtbarkeitsebenen sind in den funktionalen Docs bereits skizziert. Es fehlt die stringente technische Schicht: Vereinszugehörigkeit, aktiver Vereinskontext, effektive Berechtigungen pro Anfrage und konsequente Filterung bei club-sichtbaren Objekten.


3. Ist-Stand im Code (Gap-Analyse)

3.1 Identität und Rollen

  • profiles.role ist eine globale Kennzeichnung (admin, superadmin, trainer, user, …).
  • Keine Tabelle für Vereinsmitgliedschaft mit Mehrfachrollen pro Verein.
  • Sessions liefern nur profile_id + globale role (auth.pyget_session).

Konsequenz: Mehrere Vereine mit unterschiedlichen Rollen pro User sind nicht modelliert; ein „Vereinsadmin“ kann nicht sauber von einem reinen Trainer unterschieden werden, sobald beides nur über profiles.role laufen soll.

3.2 Organisation & APIs

  • clubs, divisions, training_groups existieren (002_organization.sql).
  • GET /api/clubs listiert alle Vereine für jeden eingeloggten Nutzer.
  • POST /api/clubs erlaubt Anlage für trainer und user nicht nur Systemadmin.
  • Sparten/Gruppen: Schreibzugriff über globale admin/superadmin, nicht über Vereinsadmin im Kontext „sein Verein“.

Konsequenz: Weder Datenisolation noch Produktdifferenzierung „nur Systemadmin legt Verein an“ sind umgesetzt.

3.3 Trainingsplanung

  • Zugriff auf Einheiten gruppenbasiert: Trainer/Co-Trainer der training_groups, plus lead_trainer_profile_id (Migration/Pfad training_planning).
  • _assert_club_visible_for_trainer bindet Vereinssicht für Teile der Planung an „aktive Gruppe als Trainer/Co im Verein“ kein generelles Mitgliedschaftsmodell.

Konsequenz: Planung ist gruppenzentriert, nicht mitgliedschaftszentriert; Vereinsweite Aufgaben des Vereinsadmins fehlen als konsistentes Recht.

3.4 Governance / Sichtbarkeit (kritisch)

  • Übungen (list_exercises): Bedingung sinngemäß „official OR club OR created_by = ich“ club gilt für alle Mandanten, ohne Prüfung exercise.club_id ∈ Vereine des Nutzers.
  • Detailzugriff private: nur Owner ok.
  • Rahmenprogramme (training_framework_programs): Lesen fremder Rahmen über visibility=club ist in _framework_access nicht gelöst (faktisch stark creator-basiert für Nicht-Admins).

Konsequenz: Cross-Tenant-Leaks bei als club markierten Bibliotheksobjekten sind möglich bzw. Leselogik ist inkonsistent zwischen Modulen.

3.5 Frontend

  • AuthContext: nur globales Profil, kein activeClubId, keine Mitgliedschaften.

3.6 Membership (kommerziell/limits)

  • Mitai-artige Tabellen (tiers, subscriptions, tier_limits, …) sind nutzerbezogen, nicht vereinsbezogen.
  • Kein konzipiertes club_subscription / club_plan im Schema.

4. Zielarchitektur

4.1 Begriffe

Begriff Definition
Mandant Immer ein Verein (clubs.id).
Systemadmin Global (profiles.role oder dediziertes Flag); darf Plattform-weite Objekte und Vereinslifecycle (Anlegen, Zuweisen Hauptverwalter).
Vereinskontext Pro Session gewählter aktiver Verein (active_club_id), wenn der User Mitglied ist.
Vereinsmitgliedschaft Zeile in einer Junction: User ↔ Verein ↔ eine oder mehrere Rollen.
Effektive Berechtigung Funktion aus: globale Rolle, Mitgliedschaft im aktiven Verein, optional Sparte/Gruppe, Sichtbarkeit des Objekts.

4.2 Rollenmodell (Schichten)

Schicht A Plattform (global, kleine Menge)

  • system_admin / superadmin (bestehende Semantik konsolidieren und benennen).

Schicht B Verein (pro Mitgliedschaft, mehrere Rollen möglich)

  • club_admin Hauptverwalter:in (ein Verein genau eine „primäre“ Admin-Zuweisung empfohlen, siehe 4.4).
  • division_lead Spartenverantwortliche:r (Scope: division_id optional an Mitgliedschaft gebunden).
  • trainer Trainer/Übungsleitung (Abgrenzung zu Co-Trainer siehe Gruppe).
  • content_editor Redakteur:in / Inhaltsverantwortliche:r (fachlich wie Anforderungsdoc).

Schicht C Abgeleitet aus Trainingsgruppe (bereits teilweise vorhanden)

  • Haupttrainer / Co-Trainer über training_groups.trainer_id und co_trainer_ids (und ggf. lead_trainer_profile_id auf Einheit).

Mapping Alt → Neu: Bestehendes profiles.role kann Übergangsweise als „Default-Rolle für Pilotverein“ dienen, soll aber mittelfristig nicht die einzige Quelle für Vereinsrechte sein.

4.3 Mitgliedschaft und aktiver Verein

  • Neue Kernstruktur (konzeptionell):
    club_members (profile_id, club_id, status, created_at, …)
    club_member_roles (club_member_id, role_code, optional division_id für spartenbezogene Rollen).

  • Aktiver Verein:

    • Persistenz: Nutzereinstellung (profiles.default_club_id oder eigene Tabelle profile_preferences).
    • Pro Request: Header X-Active-Club-Id oder Query (einheitlich dokumentieren); Server validiert Mitgliedschaft.
  • Tenant-Switch UI: Bei Mitgliedschaft in >1 Verein Auswahl im Frontend; alle Listen/Aktionen verwenden aktiven Kontext.

4.4 Vereins-Lebenszyklus

  • Neuer Verein: nur Systemadmin; Pflichtfelder: Name, initialer club_admin (bestehendes Profil zuweisen oder Einladungsflow später).
  • Vereinsadmin verwaltet in „Mein Verein“: Sparten, Gruppen, Trainerzuordnungen, Einladungen (später), interne Sichtbarkeit ohne andere Vereine zu sehen.

4.5 Daten- und Funktionssicht

Datenklasse Leseregel (Ziel)
Global offiziell Alle authentifizierten (ggf. später thematisch eingeschränkt).
Verein (visibility=club, club_id gesetzt) Nur Profile mit Mitgliedschaft in diesem club_id; optional zusätzlich Sparte, wenn division_id am Objekt gepflegt wird.
Privat Nur created_by (und explizite Shares später).
Geplante Einheiten Wie heute über Gruppe + Trainer/Co; zusätzlich Vereinskontext zur Navigation/Audit.

Einheitlicher Governance-Kern bleibt wie in CURR-005; Ergänzung: division_id auf Bibliotheksobjekten, wenn „Sparte“ technisch durchgesetzt werden soll (DOMAIN_MODEL / §17).

4.6 Membership-System (Vereinsabo / Limits) Konzept

Ziel: vereinszentrierte Vertrags- und Limitlogik, analog zur bestehenden Tier-Infrastuktur, aber an club_id gebunden.

Empfohlene Bausteine:

  1. club_plans Produktdefinition (Name, Features, implizite Limits).
  2. club_subscriptions (club_id, plan_id, Status, Laufzeit).
  3. club_usage_counters oder Ableitung aus DB z.B. aktive Nutzer, aktive Trainingsgruppen (periodisch oder on-write).
  4. Enforcement-Schicht zentrale Funktion assert_club_limit(club_id, metric) vor /groups-POST, Einladungen, etc.

Offene Produktentscheidungen (vor Implementierung festlegen):

  • Zählen „Nutzer“ alle Mitglieder oder nur aktive Trainer?
  • Soft-Limits vs. Hard-Stops; Übergang für Pilotverein.

5. Umsetzungsplan (Phasen)

Phase 0 Foundations (kurz, risikoarm)

  • Begriffe und Enums in einem Ort dokumentieren (dieses Dokument + Eintrag in DATABASE_SCHEMA.md nach Migration).
  • Audit-Liste aller Router mit club_id / visibility / Listen-Endpunkten.

Phase 1 Datenmodell Mitgliedschaft & Hauptverwalter

  • Migration: club_members, club_member_roles; optional clubs.primary_admin_profile_id (oder Primär-Flag auf Mitgliedschaft).
  • Backfill: bestehende Trainer aus training_groups → minimale Mitgliedschaft trainer im jeweiligen Verein (Skript/Migration mit dokumentierter Annahme CURR-008).
  • Breaking/API: keine nur erweiterte Datenbasis.

Phase 2 Aktiver Vereinskontext & API-Kontrakte

  • Backend: Validierung X-Active-Club-Id gegen Mitgliedschaft; Hilfsfunktion get_effective_club_context(session, header).
  • GET /api/clubs für Nicht-Systemadmins: nur Vereine mit Mitgliedschaft.
  • POST /api/clubs: nur Systemadmin; Vergabe club_admin-Mitgliedschaft.
  • Frontend: Club-Switcher + Persistenz.

Phase 3 Effektive Berechtigungen (RBAC)

  • Zentrale Modulfunktion z.B. authorization/club_permissions.py:
    can(club_id, profile_id, permission, division_id=None).
  • Router schrittweise umbinden: Sparten/Gruppen CRUD nach Rolle club_admin im Kontext; Systemadmin unverändert Vollzugriff.

Phase 4 Sichtbarkeit & Leaks schließen

  • Übungen: club-Sichtbarkeit nur bei Übereinstimmung exercise.club_id mit Mitgliedschaft (und später division).
  • Gleiches Muster für training_plan_templates, training_framework_programs, Progressionsgraphen, ggf. Medien.
  • Tests: zwei Vereine, zwei Nutzer, keine Kreuzzugriffe.

Phase 5 Mitgliedschaft / Limits

  • Tabellen club_plans, club_subscriptions; Integration mit Enforcement vor relevanten Writes.
  • UI „Mein Verein“: Kennzahlenteaser oder Hinweise bei Limit (minimal).

Phase 6 Verfeinerung

  • Einladungsflow (E-Mail), Mehrfachrollen-UI, Audit-Log für Admin-Aktionen.
  • Optionale thematische Sperren (Karate vs. Gewaltschutz) als eigene Policy-Schicht.

6. Abhängigkeiten und Risiken

  • Übergang: Pilot mit einem Verein nutzt weiterhin einfache Defaults; Multi-Verein erfordert Pflicht aktiver Kontext.
  • Performance: Mitgliedschaft und Rolle sollten einmal pro Request geladen und gecacht werden (Request-Scope).
  • Konsistenz mit Mitai: Nutzer-Tiers können parallel bleiben; vereinsbezogene Limits sind die neue Quelle für Shinkan-spezifische Kaufmotive.

7. Nächste konkrete Artefakte

  1. DDL-Migrationsentwurf für Phase 1 (Review).
  2. Aktualisierung DATABASE_SCHEMA.md nach Merge der Migration.
  3. Sicherheits-Review der list_*-Endpunkte mit club-Visibility.

Letzte Aktualisierung: 2026-05-05