docs: add Phase 19 user-management CMS wiring plan + product note
This commit is contained in:
@@ -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 1–5. 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.
|
||||
Reference in New Issue
Block a user