docs: add Phase 19 user-management CMS wiring plan + product note

This commit is contained in:
daniel-c-harvey
2026-06-19 19:02:40 -04:00
parent 5298cab9b1
commit abe94953b9
2 changed files with 313 additions and 0 deletions
+58
View File
@@ -380,6 +380,64 @@ opacity + muted-text mixes are tune-on-screen details, not decision gates.
---
## Phase 19 — AuthBlocks User Management in the CMS
Wire the AuthBlocks user-administration surface (create users, manage existing accounts, manage
registration invites, manage role permissions) into the `DeepDrftManager` CMS so an admin runs account
management from inside the authenticated CMS. Daniel's framing: *"already part of the AuthBlocks library
so we just wire it up."* Correct — and **further along than it implies.** Full design, the
already-done-vs-remaining split, nav-shape alternatives, scope boundaries, and open questions:
`product-notes/phase-19-user-management-cms.md`.
**Headline finding — most of the wiring already landed by side-effect.** The AuthBlocks startup
separation (`PLAN_authblocks_trackmanager.md`, 2026-05-25) + the login/logout integration already put
the entire user-admin surface in place: `Cerebellum.AuthBlocks.Web` is referenced
(`DeepDrftManager.csproj`), `ConfigureAuthServices` registers every user-admin client + ViewModel
pointed at DeepDrftAPI (`Program.cs`), the Blazor router already discovers the AuthBlocks pages
(`Routes.razor` `AdditionalAssemblies`), they already render in `CmsLayout` (`DefaultLayout`), and the
DeepDrft `Admin` role **inherits** `UserAdmin` (so the seeded admin already passes the page gate with no
role change). The user-admin pages ship in a published **RCL** (`Cerebellum.AuthBlocks.Web` — an
`Sdk.Razor` project with no `Program.cs`), so the brief's worried-about "extract pages into an RCL" fork
**does not arise**. The API host (`api/users/*`, `api/auth/admin-register`, etc.) is already mounted on
DeepDrftAPI via `MapAuthBlocks`.
**The genuine remaining work is exposure + verification + polish, not integration.** The surface is
invisible because `CmsLayout` has **no nav menu at all** (just an app bar + Home button), so nothing
links to `/useradmin/*`. The work: (G1) add navigation; (G2) verify the wired surface end-to-end; (G3) a
legibility-only theming sweep.
**Sequenced as one real wave + verification.** `19.1 → {19.2, 19.3}`.
- **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`+) alongside
the existing CMS destinations (Catalogue / Releases / Upload); wire the canonical create-user link
(OQ2). **No service, API, data, or AuthBlocks-source change.** **Recommended nav shape: G1-b** (a real
drawer reusing AuthBlocks' own `MudNavGroup`) over an app-bar overflow stopgap or a heavier dedicated
admin dashboard.
- **19.2 — End-to-end verification (after 19.1).** Exercise list/create/deactivate users,
registrations, permissions against a running DeepDrftAPI; confirm cross-host token + CORS. Mostly
test; any break is likely a one-line config fix or an upstream AuthBlocks issue.
- **19.3 — Theming legibility sweep (after 19.1, parallel-ok).** Accept the CMS palette for the
MudBlazor-default grids; fix only contrast/legibility breaks. Bespoke restyle deferred.
**Deferred (note, don't build):** an admin dashboard landing (G1-c); working **Reset Password** (the
AuthBlocks Users page stubs it — an *upstream AuthBlocks-repo* effort, not a DeepDrft wiring task);
bespoke restyle of the AuthBlocks grids; surfacing self-service registration on the public site;
bumping `Cerebellum.AuthBlocks.Web` 10.3.33 → 10.3.35 (housekeeping, Daniel's timing).
**Open questions for Daniel (spec §6):** (1) nav shape — confirm **G1-b**; (2) canonical create-user
entry — `SuperRegister` (role multiselect, recommended) vs. `NewUser` (bare form); (3) admin dashboard
defer vs. include (recommend defer); (4) package bump now vs. separate (recommend leave); (5) confirm
Reset Password is accepted **non-functional in v1** so verification doesn't file it as a DeepDrft bug.
Items 1, 2, 5 shape the work/acceptance; 3, 4 don't block 19.1.
**Adjacency to the deferred Identity / accounts backlog item (below).** That item is about *public,
per-user* identity (favourites, listening history, playlists). This phase is *CMS-admin* account
management only — same AuthBlocks substrate, different surface. They are not the same work; this phase
does not satisfy or depend on that one.
---
## Working with this file
- **Add items by extending an existing phase first**; only create a new phase when the addition genuinely doesn't fit any of 15. Phase numbers are organisational, not sequencing.
@@ -0,0 +1,255 @@
# 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.