Skip to content

Rechte-Refactor – Umsetzungsstatus

Stand: April 2026 – Phase A bis F abgeschlossen

Dieser Bericht dokumentiert den aktuellen Stand des laufenden Refactors des Benutzer/Space/Rechte-Systems. Die Grundlage bildet die Analyse der Komplexität.

Kurzfassung

Das alte System mit vier parallelen Rollen-Konzepten, redundanten Models und verteilter Berechtigungsprüfung wurde schrittweise durch eine konsolidierte Architektur ersetzt. Die Umstellung erfolgt non-breaking ueber Dual-Write- und Dual-Read-Phasen.

Abgeschlossen (Phasen A–F):

  • Neue zentrale Models: RoleDefault, UserOnboarding
  • Namespaced Permissions (instance:*, space:*, space:member:*)
  • Einheitliche Permission-Aufloesung via getUnifiedPermissions()
  • Rollen-Resolver fuer SpaceUserTypePolicy.defaultRole (bisher ungenutzt, jetzt aktiv)
  • Dual-Write fuer UserInvitation + AccessRequestUserOnboarding
  • SpaceManagerMembership.role Mirror
  • Helper resolveInstanceRole fuer DB-First Tenant-Rollen

Offen (Phase G):

  • Die eigentliche Entfernung der alten Strukturen kann erst nach einer Beobachtungsphase von 2+ Wochen erfolgen.

Phase A – Vorbereitung

Ziel: Neue Datenstrukturen und Helper additiv anlegen, ohne den bestehenden Code zu beeinflussen.

Umgesetzt:

  • A.1 – Prisma-Models RoleDefault und UserOnboarding hinzugefuegt (Migration 13_rights_refactor_phase_a).
  • A.2 – TypeScript-Types in prilog-types/src/rights-v2.ts: UnifiedSystemRole, PermissionV2, LEGACY_PERMISSION_MAP.
  • A.3 – Seed-Service role-defaults-seed.ts. Beim Server-Start werden 6 System-Rollen synchronisiert:
    • OWNER (31 Permissions)
    • ADMIN (30)
    • SPACE_ADMIN (30)
    • MODERATOR (22)
    • MEMBER (15)
    • GUEST (6)
  • A.4unified-permissions.ts mit getUnifiedPermissions(): Single Source of Truth fuer Permission-Auflösung.
  • A.5 – 14 Unit-Tests (alle gruen).

Phase B – SpaceUserTypePolicy.defaultRole implementieren

Problem: Das Feld SpaceUserTypePolicy.defaultRole existierte im Schema, wurde aber nie vom Code verwendet. Portal-UI hatte die Einstellung seit langem, sie war jedoch wirkungslos.

Umgesetzt:

  • B.1 – Neuer Helper resolveMembershipRole() mit Prioritaetsreihenfolge:
    1. Explizit angefragte Rolle
    2. SpaceUserTypePolicy.defaultRole fuer den User-Type
    3. Fallback: MEMBER
  • B.2 – Portal-UI war bereits vorhanden, ist jetzt aktiv.
  • B.3 – Web-Client sendet keine hardcodete 'MEMBER' Rolle mehr. Das Backend laedt den userTypeId automatisch aus UserDirectoryEntry wenn er nicht mitgeschickt wird.

Praktisches Beispiel:

Admin im Portal: "Im Space 'Kollegium' sollen Lehrer automatisch MODERATOR werden"
  → SpaceUserTypePolicy.defaultRole = 'MODERATOR' gespeichert

Lehrer-Einladung wird angenommen:
  → resolveMembershipRole() findet die Policy
  → vergibt automatisch MODERATOR statt MEMBER

9 Unit-Tests fuer den Resolver.


Phase C – Namespaced Permissions

Ziel: Einheitliches Namensschema fuer Permission-Strings, ohne bestehenden Code zu brechen.

Umgesetzt:

  • services/permission-aliases.ts mit bidirektionalem Mapping alt ↔ neu.
  • hasPermission() akzeptiert beide Varianten (member:invite oder space:member:invite).
  • Clients können schrittweise migrieren, ohne dass Breaking Changes entstehen.

Namens-Schema:

AltNeu
member:invitespace:member:invite
message:readspace:message:read
file:uploadspace:file:upload
manageUsersinstance:manage_users
viewPortalinstance:view_portal

10 Unit-Tests.


Phase D – UserInvitation + AccessRequest → UserOnboarding

Ziel: Die zwei fast identischen Onboarding-Workflows zu einem konsolidierten Model zusammenlegen.

Umgesetzt:

  • services/user-onboarding-dual-write.ts mit fire-and-forget Mirror Functions.
  • Bei jedem UserInvitation.create, AccessRequest.create und Status-Transition (CLAIMED / APPROVED / REJECTED) wird zusaetzlich ein UserOnboarding-Eintrag geschrieben.
  • legacyInvitationId und legacyRequestId sichern die Rueckverfolgbarkeit.
  • reconcileOnboardingData() fuer taegliche Health-Checks.

Was noch nicht passiert ist: Die alten Tabellen user_invitations und access_requests sind noch aktiv und werden parallel geschrieben. Erst nach 2+ Wochen ohne Mismatches koennen sie in Phase G archiviert werden.


Phase E – SpaceManager → Membership.role

Ziel: Die redundante SpaceManager-Tabelle entfernen. Die Logik SpaceManager.PRIMARY = "Eigentuemer des Space" wird in Membership.role = 'OWNER' abgebildet.

Umgesetzt:

  • Bei jedem Update von SpaceManager wird die entsprechende Membership synchronisiert:
    • PRIMARYOWNER
    • DEPUTYADMIN
  • scripts/sync-space-managers-to-memberships.ts fuer einmalige Backfill-Migration bestehender Daten.

Was noch nicht passiert ist: Die SpaceManager-Tabelle bleibt bestehen (wird noch vom Portal-UI gelesen). Die vollstaendige Entfernung erfolgt in Phase G nach UI-Umstellung.


Phase F – PrilogInstanceRole DB-First

Ziel: Die Tenant-weite Rolle eines Nutzers primaer aus der Datenbank laden, nicht aus dem JWT.

Umgesetzt:

  • services/resolve-instance-role.ts mit resolveInstanceRole():
    1. Primaer: PrilogMembership.instanceRole aus DB
    2. Fallback: Portal-Role Mapping
    3. Letzter Fallback: JWT-Rollen
  • 7 Unit-Tests.

Was noch nicht passiert ist: Die bestehenden Aufrufe von mapPlatformRolesToPrilogInstanceRole() und hasPortalCapability() wurden noch nicht migriert. Diese Umstellung ist fuer Phase G vorgesehen.


Phase G – Finales Cleanup (offen, benoetigt Beobachtungsphase)

Dieser Schritt kann erst nach mindestens 2 Wochen Dual-Write ohne Mismatches erfolgen. Benoetigte Aktionen:

G.1 – Alte Funktionen entfernen

  • [ ] getDefaultRolePolicyPermissions() entfernen, nachdem alle Aufrufer auf RoleDefault umgestellt sind.
  • [ ] mapPlatformRolesToPrilogInstanceRole() entfernen, nachdem resolveInstanceRole() aktiviert ist.

G.2 – Alte Permission-Strings entfernen

  • [ ] Frontend-Code auf neue space:* / instance:* Strings migrieren.
  • [ ] LEGACY_PERMISSION_MAP entfernen.
  • [ ] hasPermission() auf strikte v2-Strings umstellen.

G.3 – Alte Tabellen archivieren

  • [ ] reconcileOnboardingData() taeglich laufen lassen und pruefen dass invitationMismatches === 0 und requestMismatches === 0.
  • [ ] Portal-UI von user_invitations / access_requests auf user_onboarding umstellen.
  • [ ] Nach 2 Wochen: Backup der alten Tabellen erstellen, dann droppen.
  • [ ] SpaceManager aus Portal-UI entfernen, danach Tabelle droppen.

G.4 – Dokumentation finalisieren

  • [ ] Entwickler-Guide fuer das neue Permission-System.
  • [ ] Migration-Anleitung fuer Custom-Tenants.
  • [ ] Admin-Handbuch aktualisieren.

Sicherheits-Checkliste fuer den Cutover

Bevor alte Strukturen entfernt werden:

  1. Backup aller betroffenen Tabellen in Production.
  2. Reconciliation-Script muss 7 Tage in Folge ohne Mismatches durchlaufen.
  3. Feature-Flag USE_NEW_PERMISSIONS=true in Staging testen.
  4. Rollback-Plan dokumentieren.
  5. Monitoring auf 403-Errors nach Umstellung erhoehen.

Commits dieser Iteration

PhaseCommitRepoBeschreibung
A.16ad75c2backend-apiRoleDefault + UserOnboarding Prisma-Models
A.2391dab8prilog-typesrights-v2.ts
A.380f71e8backend-apiSeed-Service mit 6 System-Rollen
A.4870ee0ebackend-apiunified-permissions.ts
A.57d0025dbackend-api14 Unit-Tests
Fix19e8e36backend-apitenantId NOT NULL Fix
B.1fd9f5bfbackend-apiresolveMembershipRole + 9 Tests
B.2+384a5163backend-apiuserTypeId Auto-Load
B.36b98afeweb-clientaddSpaceMember ohne hardcoded Rolle
C5be5b07backend-apiPermission-Aliases + Dual-Support
Dc4fbdaabackend-apiUserOnboarding Dual-Write
E82dff32backend-apiSpaceManager → Membership Mirror
Fa110777backend-apiresolveInstanceRole mit DB-First

Gesamt: 13 Commits in 2 Repos, ca. 30 neue Dateien, 40 neue Unit-Tests.