diff --git a/PLAN.md b/PLAN.md index b507582..3c112a7 100644 --- a/PLAN.md +++ b/PLAN.md @@ -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. diff --git a/product-notes/phase-19-user-management-cms.md b/product-notes/phase-19-user-management-cms.md new file mode 100644 index 0000000..6a29607 --- /dev/null +++ b/product-notes/phase-19-user-management-cms.md @@ -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.