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(aMudNavGroupwith the three user-adminMudNavLinks, itself wrapped in aHierarchicalRoleAuthorizeViewso it only renders forUserAdmin+). - 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 atapiBaseUrl.
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:
- Package reference.
DeepDrftManager.csproj:11referencesCerebellum.AuthBlocks.Web(10.3.33), which transitively bringsAuthBlocksWeb.Client,AuthBlocksLib,AuthBlocksModels. - Service wiring.
Program.cs:35callsAuthBlocksWeb.Startup.ConfigureAuthServices(builder.Services, contentApiUrl)— so the user-admin clients and ViewModels are already in the container, already pointed at DeepDrftAPI. - Page discovery.
Routes.razor:2setsAdditionalAssemblies="new[] { typeof(AuthBlocksWeb._Imports).Assembly }"andProgram.cs:131mirrors 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. - Default layout.
Routes.razor:6setsDefaultLayout="typeof(Layout.CmsLayout)". Since the AuthBlocks pages declare no@layout, they already render inside CmsLayout chrome. - Role gating already satisfied. The pages gate on
SystemRoleConstants.UserAdmin. The DeepDrft admin is seeded in roleAdmin, andSystemRole(id 1,Admin) is the parent ofUserAdmin(id 2) —Admin.InheritsFrom/hierarchical authorize means the existing admin already passes theUserAdmingate with no role change, no new seed, no DB edit. - Auth-state + redirect plumbing.
AuthorizeRouteViewwithRedirectToLogin/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 pages — CmsLayout 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 MudIconButton — no 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 fewMudIconButtons) 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
MudDrawernav in CmsLayout. Add a left drawer (toggle in the app bar) holding the existing primary destinations (Catalogue, Releases, Upload) and the shippedUserAdminMenufragment (which self-gates toUserAdmin+). 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), whichUserAdminMenuis already authored against — it is aMudNavGroup. 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/userslists users (theUsersClient→api/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-registerisUserAdmin-gated server-side and the admin's token must carry the role claim end-to-end./useradmin/registrationslists + creates an invite;/useradmin/permissionsreads + 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 exist —
SuperRegister(/account/superregister, role-multiselect, callsadmin-register) andNewUser(/useradmin/users/new, theModelViewcreate 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
MudDrawernav inCmsLayoutmountingUserAdminMenu(+ 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
Userspage 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/registerinvite 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 toCmsLayout.razor; mount the shippedUserAdminMenufragment (self-gates toUserAdmin+) 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.cssif the drawer needs sizing). No service, API, data, or AuthBlocks-source change.- Acceptance: an authenticated
Adminsees a nav drawer; the User Administration group appears and links to Users / Registrations / Permissions; a non-UserAdminuser (if any exist) does not see the group; existing CMS destinations are reachable from the same drawer.
- Acceptance: an authenticated
- 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
- Nav shape (G1). Confirm G1-b (real
MudDrawernav mountingUserAdminMenu+ 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. - Canonical "create user" entry. AuthBlocks ships two create paths:
SuperRegister(/account/superregister, role multiselect, callsadmin-register) andNewUser(/useradmin/users/new, theModelViewcreate form). Which is the one the nav points at? RecommendSuperRegister— it has the role-assignment multiselect inline, which is what "create an admin user" actually needs;NewUseris the bare create form. (Both can stay route-reachable; this is just which one the menu surfaces.) - 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.
- Package bump (G4) — now or separate? Bump
Cerebellum.AuthBlocks.Web10.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. - 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.