Files
familysync/.planning/phases/10-admin-role-settings/10-CONTEXT.md
T

129 lines
13 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Phase 10: Admin Role & Settings - Context
**Gathered:** 2026-06-12
**Updated:** 2026-06-13 — folded backlog 999.5 self-service onboarding INTO scope (D-07), reconciling with the ROADMAP fold (commit bcc9682, 2026-06-11). The original discuss-phase had listed it as deferred without accounting for that ROADMAP edit.
**Status:** Ready for planning
<domain>
## Phase Boundary
Phase 10 introduces an admin role and the role-gated Admin Settings surface, and carries the v1.1 DB-foundation migration that Phases 1112 consume.
**Delivers:**
- **DB foundation:** `users.is_admin` + a new `app_config` table + `calendar_events.reminder_lead_minutes` — all three shipped in this phase's migration (per the ROADMAP DB-foundation note: the v1.1 schema migration is "carried by Phase 10"). `is_admin` and `app_config` are used here; `reminder_lead_minutes` is created-now / consumed by Phase 11; `app_config.setup_complete` is created-now / consumed by Phase 12.
- **Admin role (ADMIN-03):** a role check gating admin routes/UI; non-admins cannot reach or invoke them. First-login-wins bootstrap (see D-01).
- **App-password / credential management (ADMIN-01):** an admin can view household members and rotate / re-enter a member's provider credential from the UI; validated against the provider (CalDAV PROPFIND for Fastmail) before saving, stored encrypted via the existing `crypto.ts` / `APP_PASSWORD_ENCRYPTION_KEY` path; never displayed, logged, or echoed.
- **Shared-calendar designation (ADMIN-02):** an admin picks which synced calendar is the shared family calendar (`calendars.is_shared`) from the UI, replacing the manual DB write.
- **Self-service credential onboarding (folded from backlog 999.5):** a member with no `member_credentials` row gets a `needsProviderSetup` signal and can enter / validate (CalDAV PROPFIND) / encrypt their **own** Fastmail app password on first login — the member-scoped counterpart of the admin-managed flow (see D-07), reusing the same validate→encrypt→initial-sync path. Member-scoped: a member can only set their own credential.
**NOT in this phase:** the Phase 12 setup wizard itself; per-event reminder UI/scheduling (Phase 11); full multi-provider support (backlog 999.1 — only the generic *shape* lands here, Fastmail/CalDAV is the only implemented provider). *(Self-service onboarding from backlog 999.5 is folded IN — see the Delivers bullet above and D-07.)*
</domain>
<decisions>
## Implementation Decisions
### Admin bootstrap (ADMIN-03)
- **D-01: First-login-wins.** When no admin exists, the first user to log in is flagged `is_admin=true`; subsequent users are normal members. Chosen deliberately to dovetail with Phase 12: the first login *after setup completes* becomes the admin. Must stay member-count-agnostic — the flag is a per-user boolean, not a hardcoded single-admin assumption, so more admins can be promoted later.
- **Phase-12 interaction to honor:** the "first login" that wins admin should ultimately be the first login *after* `app_config.setup_complete`. Phase 12 owns `setup_complete`; Phase 10 ships the column and the first-login-wins logic. Planner should implement the bootstrap so it reads cleanly once `setup_complete` gating is layered on in Phase 12 (e.g. "first user when zero admins exist" today, tightened to "first user after setup_complete" in P12) — do not hardcode anything that Phase 12 would have to rip out.
- **Dev note:** under `DEV_AUTH_BYPASS`, `DEV_USER` (id 1) is injected without a DB upsert. Decide and document how the dev/bypass user acquires `is_admin` for local admin-UI verification (e.g. seed id 1 as admin, or have the bypass path flag it) — see [[dev-data-user1-no-calendars]].
### Admin UI entry & gating
- **D-02: New `/admin` route.** A dedicated gated route (not an extension of the existing notifications `SettingsSheet`). An `is_admin` guard redirects non-admins away. Gives Phase 12's wizard room to grow on the same route surface. The existing avatar `SettingsSheet` (notifications toggle) stays as-is.
- **D-03: Expose `is_admin` on `/api/me`.** The PWA needs the flag to render/guard the `/admin` entry; `/api/me` currently returns only `{ id, displayName, color }` and must add `isAdmin`. The server still enforces the role on every `/api/admin/*` route — the client flag is for UX only, never the security boundary (ADMIN-03 is server-enforced).
### Credential model (ADMIN-01)
- **D-04: Generic provider shape, Fastmail-only implementation.** Add a provider/type discriminator to the credential model and frame the admin UI around "a provider credential" (avoid hardcoded "Fastmail app password" copy in the data model / API). Implement and validate ONLY Fastmail/CalDAV (PROPFIND) now. Gmail/other providers are wiring left for backlog 999.1 — do **not** build a second provider here. See [[project-nmember-expansion]] and backlog 999.1.
- **D-05: Per-member provider credential.** Keep `member_credentials` per-user; each member owns their credential row (today both rows happen to hold the same shared Fastmail account per D-16, but the model stays N-member / N-provider ready). An admin can rotate **any** member's credential. Reuse the existing encryption path; never expose the plaintext.
### Shared-calendar designation (ADMIN-02)
- **D-06: Exclusive single-select.** The admin picks exactly one synced calendar as the shared family calendar. Setting a new one clears `is_shared` on any prior shared calendar (single shared calendar, matching the core value). Picker lists the synced calendars; selection is a radio/toggle, not independent multi-toggles.
### Self-service credential onboarding (ADMIN-01 / folded from backlog 999.5)
- **D-07: Member self-service credential, member-scoped.** A member with no provider credential gets a `needsProviderSetup` signal (no `member_credentials` row) and can enter / validate (CalDAV PROPFIND) / encrypt their **own** Fastmail app password — the self-service counterpart to the admin-managed rotation (D-05), sharing the identical validate→encrypt→initial-sync path. A member can set ONLY their own credential; the cross-member rotation stays admin-only (D-05). Non-technical-friendly instructions are a **hard UX constraint**: link to Fastmail's app-password page and name the required Calendars/CalDAV scope. Never log/echo the password. Folded in per the ROADMAP edit (commit bcc9682). Reuses the same generic provider shape as D-04 (Fastmail/CalDAV only; no second provider here).
### Claude's Discretion
- **Migration packaging:** ship the full v1.1 column/table bundle (`is_admin`, `app_config`, `reminder_lead_minutes`) in one Phase-10 migration per the ROADMAP note, so Phases 11/12 don't each carry their own migration. Use `drizzle-kit generate` + `migrate` — never `push` (see [[drizzle-mariadb-push-unsafe]]).
- **`app_config` shape:** create the table now with at least a `setup_complete` flag (Phase 12). Add other global keys only as Phase 10 actually needs them; keep it a simple key/value or single-row config — planner's call.
- **`/api/admin/*` route layout:** sub-routes for members/credentials and shared-calendar — planner decides exact paths, following the existing `routes/*.ts` Hono pattern.
- Server-side admin middleware shape (a `requireAdmin` guard analogous to the existing auth middleware) — planner's call.
</decisions>
<canonical_refs>
## Canonical References
**Downstream agents MUST read these before planning or implementing.**
### Requirements & roadmap
- `.planning/REQUIREMENTS.md` — ADMIN-01, ADMIN-02, ADMIN-03 (full wording + the "Role-agnostic design" note); the DB-foundation note coupling the v1.1 migration to Phase 10; the deferred items (self-service onboarding 999.5, audit log/user-CRUD out of scope).
- `.planning/ROADMAP.md` §Phase 10 — phase goal, success criteria, dependency chain (10 → 11, 10 → 12).
- `.planning/phases/999.1-treat-fastmail-as-a-provider-support-more-calendar-providers/` — the backlog phase that D-04's generic shape is designed to plug into. Read for the provider-abstraction direction so Phase 10's credential model doesn't paint 999.1 into a corner.
### Codebase maps
- `.planning/codebase/ARCHITECTURE.md` — overall API/PWA architecture + the ordering rationale that folded the migration into Phase 10.
- `.planning/codebase/STRUCTURE.md` — where routes / schema / frontend pages live.
- `.planning/codebase/CONVENTIONS.md` — naming + module patterns to match.
### Key source files
- `apps/api/src/db/schema.ts``users` (add `is_admin`), `calendars` (`is_shared` already exists, l.89), `member_credentials` (add provider discriminator), `calendar_events` (add `reminder_lead_minutes`). New `app_config` table.
- `apps/api/src/broker/crypto.ts``encryptPassword` / `decryptPassword` (reuse for ADMIN-01).
- `apps/api/src/broker/client.ts``createDAVClient` / `fetchCalendars` (reuse for CalDAV PROPFIND credential validation).
- `apps/api/src/index.ts` — route mounting + auth middleware order (mount `/api/admin/*` behind the auth guard, add a `requireAdmin` layer).
- `apps/api/src/routes/me.ts` + `apps/api/src/auth/devBypass.ts` + `apps/api/src/auth/user.ts` — current-user resolution; where to add `isAdmin` to the `/api/me` response and bootstrap the flag on upsert.
- `apps/pwa/src/App.tsx``BrowserRouter` routes (add gated `/admin`); `SettingsSheet` entry pattern.
- `apps/pwa/src/api/client.ts``/api/me` client type (add `isAdmin`).
</canonical_refs>
<code_context>
## Existing Code Insights
### Reusable Assets
- `crypto.ts` (`encryptPassword`/`decryptPassword`, AES-256-GCM via `APP_PASSWORD_ENCRYPTION_KEY`): the exact store path ADMIN-01 must reuse — no new crypto.
- `member_credentials` table (per-user, JSON `{iv,authTag,ciphertext}` + `fastmail_email`): extend with a provider discriminator rather than replace.
- `broker/client.ts createDAVClient` + `fetchCalendars`: drives CalDAV PROPFIND — reuse to validate a credential before save (SETUP-02 also reuses this in P12).
- `auth/devBypass.ts` `c.get('user')` pattern: how routes read the current user; the `requireAdmin` guard and admin routes follow the same context-user pattern.
- `SettingsSheet` (avatar-opened): existing settings UX precedent; `/admin` is a sibling, not a replacement.
### Established Patterns
- Routes are per-feature Hono routers under `apps/api/src/routes/`, mounted in `index.ts` behind `devAuthBypass()``oidcAuthMiddleware()` on `/api/*`. Admin routes mount in the same protected band, plus a `requireAdmin` check.
- Schema migrations via `drizzle-kit generate` + `migrate` (NOT `push` — [[drizzle-mariadb-push-unsafe]]).
- Identity is `oidc_iss + oidc_sub`, never email (D-10); `upsertUser` is the bootstrap hook for first-login-wins.
- PWA routing is declarative `react-router` `<Routes>` in `App.tsx`; server state via TanStack Query, UI-only state via Zustand.
### Integration Points
- `/api/me` response → add `isAdmin`; PWA `meQuery` consumers gate the `/admin` nav entry.
- `upsertUser` (`auth/user.ts`) → first-login-wins flag write.
- `calendars.is_shared` write moves from manual DB edit to the ADMIN-02 endpoint; the calendar legend / shared-calendar consumers already read `is_shared`.
- `app_config.setup_complete` → consumed by Phase 12; `calendar_events.reminder_lead_minutes` → consumed by Phase 11.
- `needsProviderSetup` signal (member has no `member_credentials` row) → surfaced to the PWA (e.g. on `/api/me` or a dedicated endpoint — planner's call) to drive the member self-service onboarding entry (D-07); reuses the admin flow's validate→encrypt→initial-sync path, member-scoped.
</code_context>
<specifics>
## Specific Ideas
- The credential UI/data model should read as "provider credential," not "Fastmail app password" — the user explicitly wants Gmail/other providers pluggable later without reshaping the schema (D-04). Fastmail is just the first/only implemented provider.
- First-login-wins was chosen *specifically* because Phase 12's setup wizard precedes it: the operator who completes setup is the first to log in and becomes admin (D-01).
</specifics>
<deferred>
## Deferred Ideas
- **Full multi-provider support (Gmail/other)** — backlog 999.1. Phase 10 lands only the generic credential *shape*; actual additional providers + their validation are 999.1.
- ~~**Self-service credential onboarding**~~ — **folded INTO this phase** (D-07) per the ROADMAP edit (commit bcc9682, 2026-06-11); no longer deferred. The original discuss-phase listed it as deferred without accounting for that ROADMAP fold; reconciled 2026-06-13.
- **Admin audit log / health dashboard / user CRUD** — explicitly out of scope per REQUIREMENTS.md (scope creep for a small self-hosted app).
- **Multiple reminders per event** — v1.2 stretch (noted in REQUIREMENTS.md, unrelated to Phase 10).
### Reviewed Todos (not folded)
- `2026-06-10-gitea-ci-regression-and-docker-publish.md` ("Gitea CI — full regression + Docker publish") — matched only on weak generic keywords (build/api/phase); it is Phase 8 work already completed. Not relevant to Phase 10; not folded.
</deferred>
---
*Phase: 10-admin-role-settings*
*Context gathered: 2026-06-12*