Beim Umstellen von PermissionsGuard auf Fail-Closed wurde der
@Public()-Check vergessen. JwtAuthGuard ueberspringt bei @Public()
zwar die Authentifizierung, aber PermissionsGuard laeuft als
eigenstaendiger, separat registrierter APP_GUARD trotzdem mit - und
verweigerte seitdem jede Route ohne @RequirePermission/
@AnyAuthenticatedUser, selbst wenn sie @Public() war. Betroffen: health
und health/ready (die gemeldeten 403), aber ebenso Login, SSO-Flow und
die oeffentliche Herkunftsseite.
PermissionsGuard prueft jetzt @Public() als erstes und laesst diese
Routen durch, genau wie JwtAuthGuard es bereits tut. Regressionstest
ergaenzt.
Co-Authored-By: Claude Sonnet 5 <[email protected]>
PermissionsGuard erlaubte bisher jede Route ohne @RequirePermission
automatisch fuer jeden eingeloggten Nutzer (fail-open) und verliess sich
allein auf test/authorization-coverage.e2e-spec.ts, um eine vergessene
Decorator-Angabe vor dem Deploy zu erwischen - der Guard selbst hatte
kein Default-Deny.
Neuer Decorator @AnyAuthenticatedUser() macht das bisher stillschweigend
erlaubte Verhalten explizit fuer die 12 Routen, die es tatsaechlich
brauchen (ProfileController komplett, NotificationsController komplett,
AuthController.me - alle bereits vorher auf der Allowlist des e2e-Tests).
PermissionsGuard verweigert jetzt jede Route, die weder
@RequirePermission noch @AnyAuthenticatedUser traegt - eine vergessene
Decorator-Angabe schlaegt jetzt zur Laufzeit fehl (403), nicht mehr nur
im Test. Der e2e-Test prueft jetzt denselben Decorator-Vertrag statt
einer separat gepflegten Allowlist, kann also nicht mehr vom
Guard-Verhalten abweichen.
Neue Unit-Tests fuer PermissionsGuard decken beide Codepfade und das
Fail-Closed-Verhalten ab.
Co-Authored-By: Claude Sonnet 5 <[email protected]>
Dispatchte security-reviewer und code-reviewer Agenten fuer eine
vollstaendige Pruefung des RBAC-Systems (d31aa79..HEAD). Ein
KRITISCHER Fund, zwei HIGH, ein weiteres MEDIUM, alle behoben:
KRITISCH: Deaktivieren eines Benutzers hat dessen Berechtigungen nicht
entzogen. PermissionResolverService.loadPermissions() prüfte nur den
Rollen-Status, nie User.isActive - ein bereits ausgestelltes Access-
Token behielt volle Rechte bis zum Ablauf (bis zu 15 Minuten), nicht
die vorgesehenen 30 Sekunden. UsersService rief zudem nie
invalidateUser() auf. Beides jetzt behoben; neue dedizierte
permission-resolver.service.spec.ts deckt das ab (existierte bisher
gar nicht - ein Grund, warum die Luecke unbemerkt blieb).
HIGH: Kein Schutz gegen den letzten aktiven Systemrollen-Nutzer
(ADMIN). Man konnte den letzten Admin deaktivieren oder umrollen und
sich damit komplett aussperren - ohne Selbsthilfe-Weg, da
prisma/seed.ts das Seeden ueberspringt, sobald die Admin-E-Mail
bereits existiert. UsersService.deactivate()/.update() pruefen jetzt
per assertNotLastSystemRoleUser(), analog zum bestehenden
Lockout-Schutz in RolesService fuer Systemrollen selbst.
HIGH: Der Open-Meteo-Aufruf in WeatherService hatte kein Timeout und
konnte unbegrenzt haengen. Jetzt mit AbortSignal.timeout(8000) plus
sauberem Fehler-Mapping.
MEDIUM: Trachtprognose fiel bei fehlenden Wetterdaten stillschweigend
auf "MAESSIG" zurueck statt einen eigenen Unbekannt-Zustand zu zeigen
- ein Imker haette eine echte Einschaetzung gesehen, wo keine moeglich
war. Neuer Rating-Wert UNBEKANNT in Backend und Frontend.
LOW: Veralteter Kommentar an PermissionsGuard, der noch den in Phase 3
geloeschten RolesGuard erwaehnte, korrigiert.
Verifiziert: tsc, lint, build auf beiden Projekten gruen. Backend-Unit
245/245 (17 neue Tests fuer die Fixes). Frontend-Unit 14/14. Volle
Backend-e2e-Suite konnte weiterhin nicht laufen (WSL/Postgres von
Windows aus nicht erreichbar, auch nach vollem "wsl --shutdown"-
Neustart) - jeder Fix hat aber gezielte, gruene Unit-Abdeckung.
Co-Authored-By: Claude Sonnet 5 <[email protected]>
Replaces the fixed 5-value Role enum with a database-driven Role/Permission
model: admins can create custom roles with a precise per-resource/action
permission set (83-key code-owned catalog), instead of being locked to
ADMIN/BEEKEEPER/STAFF/READONLY/API_CLIENT.
- New Role/Permission/RolePermission tables; legacy Role enum renamed to
LegacyRole and kept as User.legacyRole so all existing @Roles() call
sites keep working unchanged during the migration.
- New RolesController/RolesService (CRUD, permission-set editing,
deactivate/reactivate) with lockout-prevention invariants: system roles
(ADMIN) can't be edited or deactivated, and a role still assigned to
users can't be deactivated.
- New PermissionResolverService (30s-cached, DB-fresh per user) and
PermissionsGuard + @RequirePermission(), registered as an additional
APP_GUARD alongside the legacy RolesGuard so both systems run
side-by-side until controllers are migrated in Phase 2.
- PermissionCatalogSyncService seeds any new catalog entries into the DB
on boot, so future features only touch the TS catalog.
- New GET /auth/me returns the caller's role and effective permissions.
- Migration hand-written (destructive column rename) with grants derived
from a fresh grep of every existing @Roles() call site, verified
against 5 controllers read in full for STAFF's exact write boundary.
- test/roles.e2e-spec.ts covers the new endpoints and invariants end to
end; test/authorization-coverage.e2e-spec.ts added but skipped until
Phase 2 gives every controller a @RequirePermission.
Co-Authored-By: Claude Sonnet 5 <[email protected]>