7.3 KiB
Hardening Phase 1 — Admin RBAC & staff role scopes (make role checks real)
Resurrect the dead
DynamicPermissionpolicy so the seeded admin personas can actually operate the backoffice, expose the already-built role-management handlers over HTTP (delivering REQ-031), and narrow the over-broadBookingRoles.Adminbucket that hands Support/Moderation staff clinical and financial access. This is the server half of "roles aren't checked." Track: backend · Depends on: — (parallel-safe with Phase 0) · Unlocks: Phase 4 Before you start, read _shared/agent-operating-rules.md.
1. Context — where this sits
Fixes H-04, H-05 from issues.md. Every admin endpoint is gated by
[Authorize(ConstantPolicies.DynamicPermission)], whose handler passes only for the literal role
"admin" or a matching RoleClaim — but no user holds "admin", no RoleClaim is ever seeded, and the
only claim-writing service has no controller. Net effect: the seeded super_admin (09120000020) and
finance (09120000021) personas — which RUNBOOK.md documents as the sanctioned admin path — get 403 on
every admin console action. Separately, handler-level role sets are the entire authz boundary for
bookings (controllers carry blanket [Authorize]), and BookingRoles.Admin bundles five staff roles
into clinical-notes/nurse-balance/forced-transition access, violating the codebase's own
"narrowest fitting scope" convention.
What already exists (do not rebuild): the DynamicPermissionHandler/DynamicPermissionService
wiring (ServiceCollectionExtension.cs:49-50,100-103), RoleManagerService.ChangeRolePermissionsAsync
(RoleManagerService.cs:145-208 — the claim writer), the Application-layer
Features/{Role,Admin} commands/queries (handlers exist, no HTTP surface), the demo seeder
(DemoWorldSeeder.cs:279-308), tenancy checks in handlers (correct — leave alone), and
Baya.Application/Common/StaffRoles.cs + Features/PatientCareRecords/PatientAccess.cs:17 (the
narrow-scope pattern to mirror).
2. Required reading (do this first)
- issues.md H-04/H-05 — evidence with file:line; don't re-audit.
DynamicPermissionService.cs,RoleNames.cs,RoleManagerService.cs,DemoWorldDefinitions.cs/DemoWorldSeeder.cs,Features/Bookings/BookingRoles.csand the three handlers listed in H-05,Common/StaffRoles.cs,PatientAccess.cs.dev/post-phase/refinement/RUNBOOK.md:115-130anddev/shared-working-context/reports/refinement-phase-2-report.md— the docs that currently overstate what works (you will correct them).server/CLAUDE.md+server/CONVENTIONS.md(patterns, zero-new-warnings gate).- REQ-031 in
dev/shared-working-context/frontend/requests/for-backend.md(the RBAC endpoints the frontend's/admin/rolesgrid expects).
3. Scope — build this
- Make DynamicPermission passable.
DynamicPermissionService.CanAccess: treatRoleNames.SuperAdmin(andRoleNames.Admin) as a full bypass — mirroring how the rest of the codebase treats super_admin — instead of the single literal"admin".- Seed RoleClaims for the fine-grained roles (
finance,support,moderation) mapping each to the controller/action set it should reach (finance → payouts/refunds/invoices/BNPL admin; support → tickets/alerts; moderation → reviews queue). Seed via the same idempotent mechanism the demo seeder uses (or a migrationHasDataif that's the house style — check how roles themselves are seeded). A fresh clone must have a working admin console out of the box.
- Expose role management over HTTP (REQ-031). Add a
RolesController(orAdminRolesController) underControllers/V1wiring the existingFeatures/Rolehandlers: list roles (+ their claims), grant/revoke a user role, update role permissions. Gate it tosuper_adminonly. Follow the house controller conventions (snake_case routes, ApiResult envelope, versioning). Mark REQ-031 delivered infor-backend.md. - Split
BookingRoles.Admin. Introduce purpose-specific sets (suggested:BookingRoles.ClinicalAccess = [Admin, SuperAdmin],BookingRoles.Financial = [Admin, SuperAdmin, Finance], keep aStaffset where genuinely all staff belong, e.g. read-only booking lookups for ticket context). Update the users:GetCareInstructionsQuery(clinical),TransitionBookingStatusCommand(financial — it arms the payout trigger),GetNursePayableBalanceQuery(financial), and review the remainingBookingRoles.Adminconsumers (CancelSessionCommand,CancelBookingCommand,SubmitCareInstructionsCommand,GetVisitVerificationQuery,ListBookingsQuery,GetBookingDetailQuery,GetBookingRequestQuery) — assign each the narrowest set that matches what it exposes. Justify each choice in the report. - Docs honesty. Correct
RUNBOOK.md+ the refinement-phase-2 report claims about the admin personas; document the finance/support/moderation scopes somewhere durable (server/CLAUDE.md or a product note).
4. Mocks & seams in this phase
None. This is authorization wiring only — no vendor seams, no schema beyond possible RoleClaim seed rows.
5. Critical rules you must not get wrong
- Do not weaken anything: endpoints currently gated by DynamicPermission must stay gated; you are
making the policy satisfiable, not optional. No
[AllowAnonymous]anywhere in this phase. - Tenancy behavior (404-not-403 on cross-tenant) is correct today — don't touch those handlers except for the role-set swap.
- The role-claim seed must be idempotent (the seeder runs on every Dev boot) and must not touch production posture (respect the refinement-phase-5 config guard patterns).
- Support/Moderation must lose clinical + financial access, but must NOT lose what tickets genuinely need (booking summary context for a thread). Check the ticket flows before narrowing a shared query.
- Zero new build warnings; enum/role names are stable wire contracts — don't rename
super_adminetc.
6. Definition of Done
- Integration tests (Testing env, house
WebApplicationFactorypattern): super_admin 200s on a representative endpoint of every admin console family; finance 200s on payouts + 403s on verification decide; support 403s onGET bookings/{id}/care_instructionsand onTransitionBookingStatus; moderation 200s on the review queue only. Grant/revoke via the new controller round-trips. dotnet build Baya.sln0 new warnings ·dotnet test Baya.slngreen (all prior tests + new).- REQ-031 marked delivered; RUNBOOK/report corrections committed.
7. How to test (human)
- Boot per RUNBOOK; log in as 09120000020 (super_admin) via dev OTP.
- Call
GET api/v1/platform_configs, the payout batches list, the moderation queue — all 200. - Log in as 09120000021 (finance): payouts 200; verification decide → 403.
- Grant
moderationto a fresh user via the new RolesController; verify the queue opens for them.
8. Hand off & document
- Tick H-04/H-05 in issues.md with the commit hash.
- Update
for-backend.md(REQ-031),server/CLAUDE.md(authz model paragraph), RUNBOOK. - Write
dev/shared-working-context/reports/hardening-phase-1-report.md+ a backend handoff note if Phase 4 runs as a separate agent.