diff --git a/.planning/REQUIREMENTS.md b/.planning/REQUIREMENTS.md index 2c7367c..8793b8c 100644 --- a/.planning/REQUIREMENTS.md +++ b/.planning/REQUIREMENTS.md @@ -29,13 +29,13 @@ _Last updated: 2026-05-21_ ### Users & Admin (ADMIN) -- [ ] **ADMIN-01**: Admin can create user accounts (email, temporary password that must be changed on first login) -- [ ] **ADMIN-02**: Admin can deactivate a user account (blocks all logins and API access; data preserved) -- [ ] **ADMIN-03**: Admin can initiate password reset for a user (sends reset email; does not grant admin access to the account) -- [ ] **ADMIN-04**: Admin can view and adjust individual user storage quotas (warns if new limit is below current usage) -- [ ] **ADMIN-05**: Admin can assign AI provider and model per user (users cannot modify their own AI configuration) +- [x] **ADMIN-01**: Admin can create user accounts (email, temporary password that must be changed on first login) +- [x] **ADMIN-02**: Admin can deactivate a user account (blocks all logins and API access; data preserved) +- [x] **ADMIN-03**: Admin can initiate password reset for a user (sends reset email; does not grant admin access to the account) +- [x] **ADMIN-04**: Admin can view and adjust individual user storage quotas (warns if new limit is below current usage) +- [x] **ADMIN-05**: Admin can assign AI provider and model per user (users cannot modify their own AI configuration) - [ ] **ADMIN-06**: Admin can view audit log filtered by date range, user, and action type (metadata only — no document content, filenames, or extracted text) -- [ ] **ADMIN-07**: Admin impersonation ("log in as user") is explicitly excluded by architecture — no endpoint or UI pathway exists +- [x] **ADMIN-07**: Admin impersonation ("log in as user") is explicitly excluded by architecture — no endpoint or UI pathway exists ### Storage & Infrastructure (STORE) diff --git a/.planning/ROADMAP.md b/.planning/ROADMAP.md index 568800b..b5cd265 100644 --- a/.planning/ROADMAP.md +++ b/.planning/ROADMAP.md @@ -5,7 +5,7 @@ _Last updated: 2026-05-22_ ## Phases - [x] **Phase 1: Infrastructure Foundation** — PostgreSQL + MinIO wired into Docker Compose; Alembic migrations running; existing app still works -- [ ] **Phase 2: Users & Authentication** — Full auth flow end-to-end (register, login, TOTP, backup codes, password reset, sign-out-all) with admin panel for user management +- [x] **Phase 2: Users & Authentication** — Full auth flow end-to-end (register, login, TOTP, backup codes, password reset, sign-out-all) with admin panel for user management - [ ] **Phase 3: Document Migration & Multi-User Isolation** — All documents in PostgreSQL + MinIO; per-user isolation enforced; existing UI still works - [ ] **Phase 4: Folders, Sharing, Quotas & Document UX** — Full document management UX (folders, sharing, quota bar, PDF preview, search, audit log) - [ ] **Phase 5: Cloud Storage Backends** — Users can connect OneDrive, Google Drive, Nextcloud, or WebDAV as a personal storage backend @@ -57,13 +57,13 @@ _Last updated: 2026-05-22_ - [x] 02-02-PLAN.md — Register/login (TOTP + backup code paths) + refresh/logout/change-password endpoints + CSP/Origin validation/rate-limit (IP + per-account) + Vue auth store + router guard + Login/Register views **Wave 3** *(blocked on Wave 2 completion)* -- [ ] 02-03-PLAN.md — TOTP enrollment + backup codes + password reset + sign-out-all endpoints + AccountView + TotpEnrollment + BackupCodesDisplay + PasswordReset views +- [x] 02-03-PLAN.md — TOTP enrollment + backup codes + password reset + sign-out-all endpoints + AccountView + TotpEnrollment + BackupCodesDisplay + PasswordReset views **Wave 4** *(blocked on Wave 3 completion)* -- [ ] 02-04-PLAN.md — Admin backend: user CRUD, quota, AI config endpoints with get_current_admin enforced + tests +- [x] 02-04-PLAN.md — Admin backend: user CRUD, quota, AI config endpoints with get_current_admin enforced + tests **Wave 5** *(blocked on Wave 4 completion)* -- [ ] 02-05-PLAN.md — Admin panel frontend: AdminView + three tab components + AppSidebar admin link and user identity footer +- [x] 02-05-PLAN.md — Admin panel frontend: AdminView + three tab components + AppSidebar admin link and user identity footer **Cross-cutting constraints:** - JWT access token in Pinia memory only — never localStorage (Plans 02, 03, 05) @@ -133,7 +133,7 @@ _Last updated: 2026-05-22_ | Phase | Plans Complete | Status | Completed | |-------|----------------|--------|-----------| | 1. Infrastructure Foundation | 5/5 | Complete | 2026-05-22 | -| 2. Users & Authentication | 0/5 | Planned | - | +| 2. Users & Authentication | 5/5 | Complete | 2026-05-22 | | 3. Document Migration & Multi-User Isolation | 0/? | Not started | - | | 4. Folders, Sharing, Quotas & Document UX | 0/? | Not started | - | | 5. Cloud Storage Backends | 0/? | Not started | - | diff --git a/.planning/STATE.md b/.planning/STATE.md index d0380b5..1bc50a2 100644 --- a/.planning/STATE.md +++ b/.planning/STATE.md @@ -4,19 +4,19 @@ milestone: v1.0 milestone_name: milestone current_phase: 2 status: in_progress -last_updated: "2026-05-22T18:06:00Z" +last_updated: "2026-05-22T18:30:00Z" progress: total_phases: 5 - completed_phases: 1 + completed_phases: 2 total_plans: 10 - completed_plans: 9 - percent: 34 + completed_plans: 10 + percent: 40 --- # Project State **Project:** DocuVault -**Status:** Phase 2 In Progress — Executing +**Status:** Phase 2 Complete — Phase 3 Ready **Current Phase:** 2 **Last Updated:** 2026-05-22 @@ -25,16 +25,16 @@ progress: | Phase | Name | Status | |---|---|---| | 1 | Infrastructure Foundation | ✓ Complete | -| 2 | Users & Authentication | In Progress (4/5 plans complete) | +| 2 | Users & Authentication | ✓ Complete (5/5 plans) | | 3 | Document Migration & Multi-User Isolation | Not Started | | 4 | Folders, Sharing, Quotas & Document UX | Not Started | | 5 | Cloud Storage Backends | Not Started | ## Current Position -**Phase:** 02-users-authentication — In Progress -**Plan:** 4/5 complete (Plan 04: Admin backend API) -**Progress:** ████░░░░░░ 34% (1/5 phases + 4/5 Phase 2 plans) +**Phase:** 02-users-authentication — Complete +**Plan:** 5/5 complete (Plan 05: Admin panel frontend) +**Progress:** ████░░░░░░ 40% (2/5 phases complete) ## Performance Metrics @@ -43,7 +43,7 @@ progress: | Phases complete | 1 / 5 | | Requirements mapped | 54 / 54 | | Plans written | 5 (Phase 1) | -| Plans complete | 9 (5 Phase 1 + 4 Phase 2) | +| Plans complete | 10 (5 Phase 1 + 5 Phase 2) | ## Accumulated Context @@ -82,6 +82,8 @@ progress: | ADMIN-07 enforced by omission | No impersonation endpoint exists; AST check + test_admin_impersonation_not_found verify absence; violates privacy-first core value | | _user_to_dict() whitelist for admin responses | Explicit field whitelist prevents accidental password_hash/credentials_enc leakage from admin endpoints | | Quota warning is 200 not 4xx | Below-usage limit change is applied; warning=True advisory field returned — not a rejection | +| AdminQuotasTab fetches quotas per-user via Promise.allSettled | adminListUsers() does not include quota fields; per-user endpoint parallelized; failed quotas filtered silently | +| Temp password via crypto.getRandomValues | Browser-native CSPRNG; no external library; always satisfies AUTH-01 strength rules | ### Open Questions @@ -100,6 +102,6 @@ _Updated at each phase transition._ | Field | Value | |---|---| -| Last session | 2026-05-22 — Executed Phase 2 Plan 04 (Admin backend API: user CRUD, quota, AI config) | -| Next action | Run `/gsd:execute-phase 2` to continue Phase 2 (Plan 05: admin panel frontend) | +| Last session | 2026-05-22 — Executed Phase 2 Plan 05 (Admin panel frontend: AdminView, three tab components, AppSidebar update) | +| Next action | Run `/gsd:discuss-phase 3` or `/gsd:plan-phase 3` to begin Phase 3 (Document Migration & Multi-User Isolation) | | Pending decisions | See Open Questions above | diff --git a/.planning/phases/02-users-authentication/02-05-SUMMARY.md b/.planning/phases/02-users-authentication/02-05-SUMMARY.md new file mode 100644 index 0000000..d63f14f --- /dev/null +++ b/.planning/phases/02-users-authentication/02-05-SUMMARY.md @@ -0,0 +1,170 @@ +--- +phase: 02-users-authentication +plan: 05 +subsystem: ui +tags: [vue3, pinia, tailwind, admin, user-management, quota, ai-config] + +# Dependency graph +requires: + - phase: 02-users-authentication + plan: 02 + provides: "useAuthStore (user.role, logout()), api/client.js request() with Bearer injection, router with /admin route" + - phase: 02-users-authentication + plan: 03 + provides: "ConfirmBlock.vue component pattern, AccountView.vue tab pattern reference" + - phase: 02-users-authentication + plan: 04 + provides: "backend/api/admin.py: all 7 admin endpoints (list/create/status/password-reset/quota/ai-config)" +provides: + - "frontend/src/views/AdminView.vue: tabbed admin panel (Users | Quotas | AI Config)" + - "frontend/src/components/admin/AdminUsersTab.vue: user CRUD with create form, inline deactivation confirmation, reactivate, reset-password" + - "frontend/src/components/admin/AdminQuotasTab.vue: quota inline edit with MB display, usage %, below-usage warning" + - "frontend/src/components/admin/AdminAiConfigTab.vue: AI provider/model dropdowns per user with Saved confirmation" + - "frontend/src/components/layout/AppSidebar.vue: conditional Admin link + user identity footer with sign-out" +affects: [03-user-document-isolation, 04-folders-sharing-quotas] + +# Tech tracking +tech-stack: + added: [] # No new packages + patterns: + - "Per-row pending state via reactive({}) keyed by user id — prevents double-click and shows row-level spinner without blocking other rows" + - "Promise.allSettled for parallel quota fetching — N+1 calls parallelized; failed quotas silently filtered rather than failing entire tab" + - "crypto.getRandomValues for temp password generation — browser-native CSPRNG, no external library" + - "Inline confirmation pattern (v-if on confirmDeactivate === user.id) — replaces action cell in-place per UI-SPEC" + - "1.5s savedId timeout for Save confirmation — mirrors UI-SPEC 'Saved' flash spec exactly" + +key-files: + created: + - "frontend/src/views/AdminView.vue — tabbed admin panel, imports three tab components" + - "frontend/src/components/admin/AdminUsersTab.vue — user table with create/deactivate/reactivate/reset-password" + - "frontend/src/components/admin/AdminQuotasTab.vue — quota inline-edit table with warning" + - "frontend/src/components/admin/AdminAiConfigTab.vue — AI provider/model per-user table" + modified: + - "frontend/src/components/layout/AppSidebar.vue — admin link (role-gated) + user identity footer" + - "frontend/src/api/client.js — fixed 4 admin function bugs (wrong URLs + wrong field names)" + +key-decisions: + - "AdminQuotasTab fetches quotas per-user via Promise.allSettled rather than expecting quota data in adminListUsers() — backend adminListUsers() does not include quota fields; per-user endpoint exists and calls are parallelized" + - "client.js admin function URLs corrected to match actual backend: PATCH /status (not POST /deactivate+/reactivate), /password-reset (not /reset-password), ai_provider/ai_model fields (not provider/model)" + - "Temp password generation uses crypto.getRandomValues with alphanumeric+special charset; always satisfies AUTH-01 (12+ chars, upper, lower, digit, special)" + - "signOut function in AppSidebar uses async logout then router.push('/login') — consistent with AccountView pattern" + +patterns-established: + - "Admin tab pattern: AdminView.vue delegates to child tab components via v-if; each tab is self-contained with its own onMounted fetch" + - "Row-level loading: reactive({}) keyed by entity id with delete after async op — avoids full-table loading state for row-scoped actions" + +requirements-completed: [ADMIN-01, ADMIN-02, ADMIN-03, ADMIN-04, ADMIN-05, ADMIN-07] + +# Metrics +duration: ~20min +completed: 2026-05-22 +--- + +# Phase 2 Plan 05: Admin Frontend Summary + +**Tabbed admin panel with user CRUD (inline create/deactivate/reactivate/reset), quota inline edit with below-usage warning, AI provider/model assignment per user, and conditional admin link with identity footer in sidebar** + +## Performance + +- **Duration:** ~20 min +- **Started:** 2026-05-22T18:10:00Z +- **Completed:** 2026-05-22T18:30:00Z +- **Tasks:** 2 auto tasks (Task 1 was checkpoint — skipped as continuation) +- **Files created:** 4, Files modified: 2 + +## Accomplishments + +- AdminView.vue with UI-SPEC horizontal tab strip (indigo-600 active border) delegating to three self-contained tab components +- AdminUsersTab.vue: user table, inline create user form with crypto.getRandomValues temp password + copy button, inline deactivation confirmation per UI-SPEC copywriting ("Deactivate [email]? They will lose access..."), row-level loading spinners, empty state +- AdminQuotasTab.vue: quota data fetched via Promise.allSettled across all users, MB display, usage %, inline edit with save/cancel, amber warning when new limit below current usage +- AdminAiConfigTab.vue: provider dropdown (4 options) + model text input per user, "Saved" flash confirmation for 1.5s +- AppSidebar.vue: shield-icon Admin link (v-if role='admin'), user identity footer (initials avatar, email, aria-labeled sign-out button) +- Fixed 4 bugs in existing client.js admin functions (wrong endpoint paths + wrong field names) +- No impersonation UI anywhere — verified by acceptance criteria grep + +## Task Commits + +1. **Task 2: Admin tab components and AdminView** - `9137f41` (feat) +2. **Task 3: AppSidebar admin link and user identity footer** - `92e3d75` (feat) + +**Plan metadata:** (committed with final docs commit) + +## Files Created/Modified + +- `frontend/src/views/AdminView.vue` — tabbed admin panel, three tab component imports +- `frontend/src/components/admin/AdminUsersTab.vue` — user table + create form + deactivate/reactivate/reset actions +- `frontend/src/components/admin/AdminQuotasTab.vue` — quota inline edit table with usage % and below-usage warning +- `frontend/src/components/admin/AdminAiConfigTab.vue` — AI provider/model dropdowns per user +- `frontend/src/components/layout/AppSidebar.vue` — admin link + identity footer added; existing nav links unchanged +- `frontend/src/api/client.js` — 4 admin API functions corrected + +## Decisions Made + +- **Promise.allSettled for quota fetching:** adminListUsers() response does not include quota data (backend _user_to_dict() omits it). adminGetUserQuota() added to client.js for per-user fetching; parallelized via Promise.allSettled to avoid sequential N+1 latency. Failed quotas filtered rather than erroring the whole tab. +- **client.js URL bug fixes (Rule 1):** Four existing admin API functions had incorrect endpoint paths/field names: adminDeactivateUser used POST /deactivate (backend: PATCH /status), adminReactivateUser used POST /reactivate (backend: PATCH /status), adminResetUserPassword used /reset-password (backend: /password-reset), adminUpdateAiConfig sent {provider, model} (backend: {ai_provider, ai_model}). Fixed as Rule 1 auto-fixes — these would have caused 404/422 errors on every admin action. + +## Deviations from Plan + +### Auto-fixed Issues + +**1. [Rule 1 - Bug] Four incorrect admin API endpoints/field names in client.js** +- **Found during:** Task 2 pre-implementation review of existing client.js vs admin.py +- **Issue:** `adminDeactivateUser` used `POST /api/admin/users/{id}/deactivate` (404); backend uses `PATCH /api/admin/users/{id}/status`. `adminReactivateUser` used `POST .../reactivate` (404); same fix. `adminResetUserPassword` used `/reset-password` (404); backend uses `/password-reset`. `adminUpdateAiConfig` sent `{provider, model}` but backend expects `{ai_provider, ai_model}` (422). +- **Fix:** Corrected all four functions to match actual backend endpoint paths and field names. Added `adminGetUserQuota` for the quota tab's per-user quota fetching. +- **Files modified:** `frontend/src/api/client.js` +- **Verification:** Build passes, no TypeErrors, acceptance criteria all pass +- **Committed in:** `9137f41` (Task 2 commit) + +--- + +**Total deviations:** 1 auto-fixed (Rule 1 — 4 wrong API endpoints/field names in existing client.js) +**Impact on plan:** Critical fix — without it, all admin deactivate/reactivate/reset/AI-config actions would return 404 or 422. No scope creep; same task commit. + +## Issues Encountered + +None beyond the client.js bug fix above. + +## Known Stubs + +None — all four admin tab components are fully wired to real API endpoints. + +## Threat Flags + +All STRIDE mitigations from the plan's threat model are implemented: +- T-02-30: `v-if="authStore.user?.role === 'admin'"` on Admin nav link — non-admins see no admin UI +- T-02-31: Zero impersonation UI — verified by grep in automated acceptance check +- T-02-32: Admin components only bind to safe fields (email, role, is_active, ai_provider, ai_model, limit_bytes, used_bytes) — _user_to_dict() on backend excludes password_hash/credentials_enc +- T-02-33: Inline deactivation confirmation shows user email per UI-SPEC; "Keep account" cancel button present +- T-02-34: No rate limit on admin user creation — accepted risk per plan + +No new threat surface beyond what was planned. + +## Next Phase Readiness + +- Complete admin panel frontend wired to all 7 admin API endpoints +- Phase 2 (02-users-authentication) fully complete: auth backend (02-01 through 02-04) + admin frontend (02-05) +- Phase 3 (user document isolation) can proceed: authenticated user context available via useAuthStore, admin user management operational + +--- + +## Self-Check: PASSED + +**Files verified:** + +- `frontend/src/views/AdminView.vue` — FOUND, contains "Admin panel" and three tab component imports +- `frontend/src/components/admin/AdminUsersTab.vue` — FOUND, contains adminListUsers, adminDeactivateUser, adminResetUserPassword, "No users yet" +- `frontend/src/components/admin/AdminQuotasTab.vue` — FOUND, contains adminUpdateQuota, MB, warning +- `frontend/src/components/admin/AdminAiConfigTab.vue` — FOUND, contains adminUpdateAiConfig +- `frontend/src/components/layout/AppSidebar.vue` — FOUND, contains useAuthStore, admin role check, aria-label, border-t border-gray-100 + +**Commits verified:** + +- `9137f41` (feat: admin tab components) — verified in git log +- `92e3d75` (feat: AppSidebar) — verified in git log + +**Build:** `npm run build` exits 0 +**No impersonation:** grep returns 0 matches for impersonate/loginAs/login-as in all admin components + +--- +*Phase: 02-users-authentication* +*Completed: 2026-05-22*