Files
deepdrft/product-notes/phase-19-user-management-cms.md
T

16 KiB

Phase 19 — AuthBlocks User Management in the CMS

Status: proposed. Author: product-designer. Date: 2026-06-19. Implementer: TBD (separate delegation).

Wire the AuthBlocks user-administration surface (create users, manage existing accounts, manage registration invites, manage role permissions) into the DeepDrftManager CMS so an admin can run account management from inside the same authenticated CMS they already use.

Daniel's framing: "this is already part of the AuthBlocks library so we just need to wire it up properly." That framing is correct — and the wiring is further along than it implies. This note's headline finding is that almost the entire integration already landed as a side-effect of the prior AuthBlocks startup separation (PLAN_authblocks_trackmanager.md, landed 2026-05-25) and the login/logout integration. What remains is a thin navigation + verification + polish slice, not an integration project. The spec below separates what is already done from the genuine remaining work so the implementer does not redo settled wiring.


1. What AuthBlocks ships, and how it is packaged

Read from local source at C:\Development\AuthBlocks (not public docs). The key question the brief raised — is the user-admin surface consumable or host-bound? — resolves cleanly: it is consumable.

The user-admin surface is a published RCL, despite the "Web" name

AuthBlocksWeb is an Microsoft.NET.Sdk.Razor project (not Sdk.Web) with no Program.cs — it is a Razor Class Library, not a runnable host. pack.ps1 packs it as Cerebellum.AuthBlocks.Web and pushes it to nuget.org alongside the other four packages. So the user-admin Razor components are distributed as a normal RCL and are consumed by reference, exactly like any MudBlazor-based component package. No extraction fork is needed — the architectural risk the brief flagged ("if the pages are host-bound and need extracting into an RCL") does not materialize. The pages are already in the RCL.

What's in the package (the consumable surface)

Components under AuthBlocksWeb/Components/:

  • Account pages (Pages/Account/): Login, Logout, Register (self-service via invite code), SuperRegister (admin-creates-account, route /account/superregister), AccessDenied.
  • User admin pages (Pages/UserAdmin/), each @page-routed and gated [HierarchicalRoleAuthorize(SystemRoleConstants.UserAdmin)]:
    • Users/Users.razor/useradmin/users — searchable user grid; per-row Reset Password (stubbed — // todo integrate with email), Deactivate/Reactivate, edit modal.
    • Users/NewUser.razor/useradmin/users/new — create-user form.
    • Registrations/Registrations.razor/useradmin/registrations — pending-invite grid (email, consumed?, dates), new-registration + edit-registration modals.
    • Permissions/Permissions.razor/useradmin/permissions — user↔role assignment.
  • Menu fragments (Components/Layout/): AccountNavMenu, UserAdminMenu (a MudNavGroup with the three user-admin MudNavLinks, itself wrapped in a HierarchicalRoleAuthorizeView so it only renders for UserAdmin+).
  • Shared (Components/Shared/): LogoutButton, StatusMessage.
  • DI entry point (Startup.cs): ConfigureAuthServices(IServiceCollection, string apiBaseUrl) registers the cascading auth state, the JWT client stack, and every user-admin client + ViewModel (UsersClient/UsersViewModel, RoleClient, UserRolesClient/PermissionsViewModel, PendingRegistrationClient/RegistrationsViewModel), all pointed at apiBaseUrl.

The pages lean on Cerebellum.BlazorBlocks.Web for their grid scaffolding (ModelView, ModelPageViewModel, ConfirmCancelModal) and MudBlazor for chrome — both already present in the CMS.

The API side is already hosted

The clients those ViewModels use call the AuthBlocks API surface, which DeepDrftAPI already mounts via app.MapAuthBlocks() (Program.cs:184): api/auth/* (incl. admin-register, gated UserAdmin; and roles), api/users/*, api/roles/*, api/user-roles/*, api/pending-registration/*. AddAuthBlocks + UseAuthBlocksStartupAsync (migrate + seed) are wired, and the Auth DB + secrets live in DeepDrftAPI/environment/. This all landed with the startup separation.


2. What is ALREADY wired in DeepDrftManager (do not redo)

Verified against the current DeepDrftManager source. These are the integration steps a naive plan would propose — and they are already done:

  1. Package reference. DeepDrftManager.csproj:11 references Cerebellum.AuthBlocks.Web (10.3.33), which transitively brings AuthBlocksWeb.Client, AuthBlocksLib, AuthBlocksModels.
  2. Service wiring. Program.cs:35 calls AuthBlocksWeb.Startup.ConfigureAuthServices(builder.Services, contentApiUrl) — so the user-admin clients and ViewModels are already in the container, already pointed at DeepDrftAPI.
  3. Page discovery. Routes.razor:2 sets AdditionalAssemblies="new[] { typeof(AuthBlocksWeb._Imports).Assembly }" and Program.cs:131 mirrors it for endpoint mapping. The Blazor router already discovers every AuthBlocksWeb page, including /useradmin/users, /useradmin/registrations, /useradmin/permissions, /useradmin/users/new, /account/superregister. They are route-reachable today by typing the URL.
  4. Default layout. Routes.razor:6 sets DefaultLayout="typeof(Layout.CmsLayout)". Since the AuthBlocks pages declare no @layout, they already render inside CmsLayout chrome.
  5. Role gating already satisfied. The pages gate on SystemRoleConstants.UserAdmin. The DeepDrft admin is seeded in role Admin, and SystemRole (id 1, Admin) is the parent of UserAdmin (id 2) — Admin.InheritsFrom/hierarchical authorize means the existing admin already passes the UserAdmin gate with no role change, no new seed, no DB edit.
  6. Auth-state + redirect plumbing. AuthorizeRouteView with RedirectToLogin / RedirectToAccessDenied (Routes.razor) already protects the surface coherently.

Net: an authenticated DeepDrft admin can, right now, navigate to /useradmin/users and the page should render and call DeepDrftAPI. The reason it feels unbuilt is that nothing in the CMS UI links to these pagesCmsLayout has no nav drawer at all (just an app bar with a Home button), so the surface is invisible and unverified.

This is the crux: the work is not integration, it is exposure + verification + fit-and-finish.


3. The genuine remaining work

G1 — Navigation: there is no way to reach the surface from the UI (the real gap)

CmsLayout.razor is an app bar + a single Home MudIconButtonno MudDrawer, no nav menu. The catalogue, releases, upload, and now user-admin surfaces are all reachable only by typed URL or in-page buttons. Mounting UserAdminMenu requires a navigation container to mount it into.

Three shapes, meaningfully different (diverge-before-converge):

  • G1-a — Minimal: app-bar overflow menu. Add a MudMenu (or a few MudIconButtons) to the existing app bar with links to the three user-admin routes (+ SuperRegister). Smallest change; keeps CmsLayout's current spare aesthetic. Cost: doesn't scale — the CMS already has catalogue/releases/upload that arguably belong in a real nav too, and an overflow menu gets crowded.
  • G1-b — Recommended: a real MudDrawer nav in CmsLayout. Add a left drawer (toggle in the app bar) holding the existing primary destinations (Catalogue, Releases, Upload) and the shipped UserAdminMenu fragment (which self-gates to UserAdmin+). This is the idiomatic Blazor/MudBlazor CMS shape, it reuses AuthBlocks' own menu component verbatim, and it gives the CMS the navigation spine it's currently missing. Cost: slightly larger CmsLayout change; a small visual-design pass on the drawer.
  • G1-c — Maximal: dedicated "Administration" section. A drawer plus a distinct admin sub-area (its own landing page summarizing user counts / pending registrations, mirroring the catalogue dashboard idiom). Cost: net-new surface (an admin dashboard) beyond what AuthBlocks ships; scope creep for v1.

Recommendation: G1-b. It solves the actual gap (no nav) with the least bespoke code, reuses the shipped UserAdminMenu, and is the natural home for the CMS's other destinations too. G1-c's admin dashboard is a good later idea (note it as deferred), not a v1 gate. G1-a is a stopgap that we'd replace with G1-b within a release.

Borrowed precedent: this is the standard MudBlazor admin-template layout (persistent left MudDrawer + MudNavMenu/MudNavGroup), which UserAdminMenu is already authored against — it is a MudNavGroup. We are adopting the pattern the component was built for, not inventing one.

G2 — Verification pass (the surface is wired but unproven end-to-end)

Because nothing exercised these pages in the CMS, treat first-light as verification, not assumption. Confirm against a running DeepDrftAPI + Auth DB:

  • /useradmin/users lists users (the UsersClientapi/users/* round-trip works cross-origin / cross-host, with the bearer token the CMS already holds).
  • /account/superregister (or /useradmin/users/new) creates a user — admin-register is UserAdmin-gated server-side and the admin's token must carry the role claim end-to-end.
  • /useradmin/registrations lists + creates an invite; /useradmin/permissions reads + assigns roles.
  • CORS / token presentation: the prior plan widened DeepDrftAPI CORS for the Manager origin for login; confirm the same allowance covers api/users/* etc. (it should — same origin, same policy).
  • Two create paths existSuperRegister (/account/superregister, role-multiselect, calls admin-register) and NewUser (/useradmin/users/new, the ModelView create form). Decide which is the canonical "create user" entry the nav points at (see OQ2); verify whichever is chosen.

This pass is where any latent break surfaces (a client config typo, a missing role claim in the CMS-issued token, a package-version mismatch). It is real work even though no code may change if it all passes.

G3 — Theming / fit-and-finish

The AuthBlocks pages are MudBlazor-default-styled and were authored against AuthBlocks' own theme, not the DeepDrft CMS palette (DeepDrftPalettes.Cms, mounted in CmsLayout with IsDarkMode="false"). Expect minor visual seams: the AuthBlocks ThemeColorDemo/MudBlazor defaults vs. the CMS's DM-Sans / charleston palette. Scope for v1: accept MudBlazor-default styling inside the CMS palette (the MudThemeProvider in CmsLayout already themes Mud components, so the pages inherit the CMS palette for free) and only fix outright legibility/contrast breaks. A deeper bespoke restyle of the AuthBlocks grids is explicitly out of v1 — flag as deferred polish.

G4 — Package version alignment (housekeeping, flag don't gate)

DeepDrftManager references Cerebellum.AuthBlocks.Web 10.3.33; AuthBlocks source is at 10.3.35. Minor lag. Bumping to 10.3.35 is low-risk and gets the latest user-admin fixes, but is not required for this phase to function. Note it; let Daniel decide whether to bump in this pass or separately.


4. Scope boundaries

In for v1:

  • G1-b: a MudDrawer nav in CmsLayout mounting UserAdminMenu (+ the existing CMS destinations).
  • G2: end-to-end verification of list/create/deactivate users, registrations, permissions.
  • G3: accept-the-palette theming; fix only legibility breaks.
  • Pick + wire the canonical "create user" entry (OQ2).

Deferred (note, don't build):

  • Admin dashboard (G1-c) — a user-admin landing summarizing counts / pending invites. Good later; not a v1 gate.
  • Reset Password — the AuthBlocks Users page stubs it (// todo integrate with email). It is an upstream AuthBlocks gap, not a DeepDrft wiring task. If Daniel wants working password reset, that's a change in the AuthBlocks repo (a new email-backed reset flow), then a version bump here — a separate effort. Do not implement password reset inside DeepDrftHome.
  • Bespoke restyle of the AuthBlocks grids to the editorial DeepDrft aesthetic.
  • Self-service public registration (/account/register invite flow) surfaced anywhere on the public site — out of scope; this phase is CMS-admin-only.
  • G4 version bump — housekeeping, Daniel's call on timing.

Explicitly not needed (the brief's worried-about fork):

  • Extracting AuthBlocks pages into a new RCL. They already ship in Cerebellum.AuthBlocks.Web.
  • New DI/service wiring, new routing, new role seeding, new Auth connection string. All present.

5. Phased breakdown (for clean dispatch)

This is a small phase. One real wave, plus verification.

  • 19.1 — CmsLayout navigation (cold-start, the only code wave). Add a MudDrawer + toggle to CmsLayout.razor; mount the shipped UserAdminMenu fragment (self-gates to UserAdmin+) and the existing CMS destinations (Catalogue /catalogue, Releases /releases, Upload /tracks/upload). Decide and wire the canonical create-user link (OQ2). Scope: CmsLayout.razor (+ a small .razor.css if the drawer needs sizing). No service, API, data, or AuthBlocks-source change.
    • Acceptance: an authenticated Admin sees a nav drawer; the User Administration group appears and links to Users / Registrations / Permissions; a non-UserAdmin user (if any exist) does not see the group; existing CMS destinations are reachable from the same drawer.
  • 19.2 — End-to-end verification (after 19.1; may surface follow-ups). Exercise G2 against a running DeepDrftAPI. Confirm list/create/deactivate/registration/permission round-trips and cross-host token + CORS. File any latent break as a follow-up (likely a one-line config fix, or an upstream AuthBlocks issue). Mostly test, not code.
  • 19.3 — Theming legibility sweep (after 19.1, parallel-ok with 19.2). Walk each user-admin page in the CMS palette; fix only contrast/legibility breaks. Defer bespoke restyle.

Dependency shape: 19.1 → {19.2, 19.3}. 19.1 is the only thing that must land first (it makes the surface reachable to verify and to view). 19.2 and 19.3 fan out behind it.


6. Open questions for Daniel

  1. Nav shape (G1). Confirm G1-b (real MudDrawer nav mounting UserAdminMenu + existing CMS destinations) over G1-a (app-bar overflow, stopgap) or G1-c (drawer + dedicated admin dashboard, more scope). Recommend G1-b. This is the load-bearing decision — it sets how much CmsLayout changes.
  2. Canonical "create user" entry. AuthBlocks ships two create paths: SuperRegister (/account/superregister, role multiselect, calls admin-register) and NewUser (/useradmin/users/new, the ModelView create form). Which is the one the nav points at? Recommend SuperRegister — it has the role-assignment multiselect inline, which is what "create an admin user" actually needs; NewUser is the bare create form. (Both can stay route-reachable; this is just which one the menu surfaces.)
  3. Admin dashboard (G1-c) — defer or include? Recommend defer. It's net-new surface beyond what AuthBlocks ships; v1 should expose the working pages, not build a new one. Flag if Daniel wants it in scope.
  4. Package bump (G4) — now or separate? Bump Cerebellum.AuthBlocks.Web 10.3.33 → 10.3.35 in this pass, or leave it? Recommend leave it unless 19.2 surfaces a fix that needs it; keep this phase a pure CMS-side wiring slice.
  5. Reset Password expectation. The Users page's Reset Password is an upstream stub. Confirm Daniel accepts it as non-functional in v1 (and that working reset is a separate AuthBlocks-repo effort), so the verification pass doesn't get filed as a DeepDrft bug.

Items 1, 2, and 5 change the shape of the work or the acceptance criteria; 3 and 4 are scope/timing calls that don't block 19.1.