# 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 `MudNavLink`s, 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 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 few `MudIconButton`s) 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 `UsersClient` → `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-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 exist** — `SuperRegister` (`/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.