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+AccessRequest→UserOnboarding SpaceManager→Membership.roleMirror- Helper
resolveInstanceRolefuer 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
RoleDefaultundUserOnboardinghinzugefuegt (Migration13_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.4 –
unified-permissions.tsmitgetUnifiedPermissions(): 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:- Explizit angefragte Rolle
SpaceUserTypePolicy.defaultRolefuer den User-Type- 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 denuserTypeIdautomatisch ausUserDirectoryEntrywenn 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 MEMBER9 Unit-Tests fuer den Resolver.
Phase C – Namespaced Permissions
Ziel: Einheitliches Namensschema fuer Permission-Strings, ohne bestehenden Code zu brechen.
Umgesetzt:
services/permission-aliases.tsmit bidirektionalem Mapping alt ↔ neu.hasPermission()akzeptiert beide Varianten (member:inviteoderspace:member:invite).- Clients können schrittweise migrieren, ohne dass Breaking Changes entstehen.
Namens-Schema:
| Alt | Neu |
|---|---|
member:invite | space:member:invite |
message:read | space:message:read |
file:upload | space:file:upload |
manageUsers | instance:manage_users |
viewPortal | instance: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.tsmit fire-and-forget Mirror Functions.- Bei jedem
UserInvitation.create,AccessRequest.createund Status-Transition (CLAIMED / APPROVED / REJECTED) wird zusaetzlich einUserOnboarding-Eintrag geschrieben. legacyInvitationIdundlegacyRequestIdsichern 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
SpaceManagerwird die entsprechendeMembershipsynchronisiert:PRIMARY→OWNERDEPUTY→ADMIN
scripts/sync-space-managers-to-memberships.tsfuer 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.tsmitresolveInstanceRole():- Primaer:
PrilogMembership.instanceRoleaus DB - Fallback: Portal-Role Mapping
- Letzter Fallback: JWT-Rollen
- Primaer:
- 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 aufRoleDefaultumgestellt sind. - [ ]
mapPlatformRolesToPrilogInstanceRole()entfernen, nachdemresolveInstanceRole()aktiviert ist.
G.2 – Alte Permission-Strings entfernen
- [ ] Frontend-Code auf neue
space:*/instance:*Strings migrieren. - [ ]
LEGACY_PERMISSION_MAPentfernen. - [ ]
hasPermission()auf strikte v2-Strings umstellen.
G.3 – Alte Tabellen archivieren
- [ ]
reconcileOnboardingData()taeglich laufen lassen und pruefen dassinvitationMismatches === 0undrequestMismatches === 0. - [ ] Portal-UI von
user_invitations/access_requestsaufuser_onboardingumstellen. - [ ] Nach 2 Wochen: Backup der alten Tabellen erstellen, dann droppen.
- [ ]
SpaceManageraus 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:
- Backup aller betroffenen Tabellen in Production.
- Reconciliation-Script muss 7 Tage in Folge ohne Mismatches durchlaufen.
- Feature-Flag
USE_NEW_PERMISSIONS=truein Staging testen. - Rollback-Plan dokumentieren.
- Monitoring auf 403-Errors nach Umstellung erhoehen.
Commits dieser Iteration
| Phase | Commit | Repo | Beschreibung |
|---|---|---|---|
| A.1 | 6ad75c2 | backend-api | RoleDefault + UserOnboarding Prisma-Models |
| A.2 | 391dab8 | prilog-types | rights-v2.ts |
| A.3 | 80f71e8 | backend-api | Seed-Service mit 6 System-Rollen |
| A.4 | 870ee0e | backend-api | unified-permissions.ts |
| A.5 | 7d0025d | backend-api | 14 Unit-Tests |
| Fix | 19e8e36 | backend-api | tenantId NOT NULL Fix |
| B.1 | fd9f5bf | backend-api | resolveMembershipRole + 9 Tests |
| B.2+3 | 84a5163 | backend-api | userTypeId Auto-Load |
| B.3 | 6b98afe | web-client | addSpaceMember ohne hardcoded Rolle |
| C | 5be5b07 | backend-api | Permission-Aliases + Dual-Support |
| D | c4fbdaa | backend-api | UserOnboarding Dual-Write |
| E | 82dff32 | backend-api | SpaceManager → Membership Mirror |
| F | a110777 | backend-api | resolveInstanceRole mit DB-First |
Gesamt: 13 Commits in 2 Repos, ca. 30 neue Dateien, 40 neue Unit-Tests.