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

123 lines
11 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
**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.
**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 credential onboarding (backlog 999.5).
</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.
### 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.
</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** (a member adds their own app password on first login) — backlog 999.5; Phase 10 is admin-managed only.
- **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*