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]>
WebhooksService.create() rejected obviously private, loopback, or
link-local URLs (incl. cloud metadata 169.254.169.254 and the
decimal/octal/hex IPv4 shorthand encodings the WHATWG URL parser
normalizes) so an admin-configured webhook can no longer be used to
proxy requests into internal infrastructure. Registration-time only,
no DNS resolution (DNS-rebinding is out of scope for this check).
WebhooksModule (WebhookSubscription: URL, Events, Secret, aktiv) mit
Event-Dispatcher, angehaengt an 10 Ereignisse in 8 bestehenden Services
(colony.created, inspection.created, queen.created, queen.replaced,
harvest.created, batch.created, batch.updated, product.stock_changed,
task.created, task.completed). Auslieferung ist best-effort und blockiert
die ausloesende Anfrage nicht bei Fehlern.