--- phase: 05-cloud-storage-backends plan: 11 type: execute wave: 1 depends_on: [] files_modified: - backend/api/admin.py - frontend/src/api/client.js - frontend/src/components/admin/AdminUsersTab.vue - backend/tests/test_admin.py autonomous: true requirements: [ADMIN-02, SEC-09] gap_closure: true must_haves: truths: - "Admin can permanently delete a non-admin user after entering their own admin password" - "Backend verifies the admin password before executing the delete" - "Delete purges cloud connections + MinIO objects + all DB rows (existing SEC-09 code runs)" - "Frontend presents an inline confirmation panel with admin password field before calling DELETE" - "Incorrect admin password returns 403 without deleting the user" artifacts: - path: "backend/api/admin.py" provides: "UserDeleteConfirm Pydantic model; delete_user handler reads admin_password from body and verifies it" - path: "frontend/src/api/client.js" provides: "adminDeleteUser(id, adminPassword) calling DELETE /api/admin/users/{id}" - path: "frontend/src/components/admin/AdminUsersTab.vue" provides: "Inline delete confirmation panel with admin password field, mirroring confirmDeactivate pattern" key_links: - from: "frontend/src/components/admin/AdminUsersTab.vue" to: "DELETE /api/admin/users/{id}" via: "adminDeleteUser(id, adminPassword)" - from: "backend/api/admin.py delete_user" to: "services.auth.verify_password" via: "verify_password(body.admin_password, admin.password_hash)" --- Add admin hard-delete with password confirmation: a backend body model that verifies the admin's own password before permanent deletion, and a frontend inline confirmation panel with password field. Purpose: The backend delete endpoint exists and correctly purges all user data, but it accepts no authentication proof for the destructive action. There is also no frontend UI to trigger it. Output: Admin can initiate deletion from the Users tab, enter their password in an inline panel, and the backend verifies the password before deleting. Incorrect password is rejected with 403. @$HOME/.claude/get-shit-done/workflows/execute-plan.md @$HOME/.claude/get-shit-done/templates/summary.md @.planning/PROJECT.md @.planning/ROADMAP.md @.planning/STATE.md @.planning/phases/05-cloud-storage-backends/05-UAT.md From backend/api/admin.py: - Existing delete_user handler: DELETE /api/admin/users/{user_id}, status 204, no request body - Already purges cloud connections + MinIO objects, writes audit log (SEC-09 — do NOT change this logic) - Uses Depends(get_current_admin) → resolves to User ORM instance as `_admin` - verify_password not currently imported; services.auth exports it: `from services.auth import verify_password` - The handler must add: parse body as UserDeleteConfirm, call verify_password(body.admin_password, _admin.password_hash), raise 403 on failure From services/auth.py (existing pattern from admin.py imports): - `hash_password(plain: str) -> str` - `verify_password(plain: str, hashed: str) -> bool` — uses pwdlib Argon2 From frontend/src/components/admin/AdminUsersTab.vue (confirmDeactivate pattern to mirror): - `confirmDeactivate = ref(null)` tracks which user ID is awaiting confirmation - `startDeactivate(id)` sets confirmDeactivate = id - Inline panel in renders when `confirmDeactivate === user.id` - Panel has confirm + cancel buttons - Model to follow: add parallel state `confirmDelete = ref(null)`, `deletePassword = ref('')`, `deleteError = ref(null)` From frontend/src/api/client.js: - All admin functions follow: request(`/api/admin/users/${id}/...`, { method, headers, body }) - DELETE with body: `request(\`/api/admin/users/${id}\`, { method: "DELETE", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ admin_password: adminPassword }) })` Task 1: Backend — UserDeleteConfirm model + password verification in delete_user backend/api/admin.py, backend/tests/test_admin.py - DELETE /api/admin/users/{id} with correct admin_password in body returns 204 and user is deleted. - DELETE /api/admin/users/{id} with wrong admin_password returns 403 {"detail": "Invalid admin password"} and user is NOT deleted. - DELETE /api/admin/users/{id} with no body returns 422 (Pydantic validation). - Cannot delete admin accounts (existing guard: 400 "Cannot delete admin accounts") — unchanged. - Cannot delete non-existent user (existing guard: 404) — unchanged. - Audit log entry written for successful delete (existing code) — unchanged. - Cloud credentials purged before DB delete (existing SEC-09 code) — unchanged. In backend/api/admin.py: 1. Add `UserDeleteConfirm` Pydantic model in the Request models section: ```python class UserDeleteConfirm(BaseModel): admin_password: str ``` 2. Add `from services.auth import verify_password` to the existing imports from services.auth (currently imports `hash_password, revoke_all_refresh_tokens`). 3. Modify the `delete_user` handler signature to accept the body: - Change `async def delete_user(user_id, request, session, _admin)` to also accept `body: UserDeleteConfirm`. - FastAPI will parse the JSON body automatically. 4. Add password verification BEFORE any deletion logic (fail fast): ```python if not verify_password(body.admin_password, _admin.password_hash): raise HTTPException( status_code=status.HTTP_403_FORBIDDEN, detail="Invalid admin password", ) ``` 5. All existing deletion logic (cloud purge, MinIO purge, audit log, session.delete) is unchanged. In backend/tests/test_admin.py, add three tests: 1. `test_delete_user_correct_password` — create admin + regular user, call DELETE with correct admin password, assert 204, assert user no longer in GET /admin/users. 2. `test_delete_user_wrong_password` — same setup, call DELETE with wrong password, assert 403, assert user still in GET /admin/users (not deleted). 3. `test_delete_user_no_body` — call DELETE with no body (or empty body), assert 422. Use the existing `_create_user_and_token(session, role="admin")` pattern from test_cloud.py (or the conftest admin_user fixture if available). cd /Users/nik/Documents/Progamming/document_scanner/backend && python -m pytest tests/test_admin.py::test_delete_user_correct_password tests/test_admin.py::test_delete_user_wrong_password tests/test_admin.py::test_delete_user_no_body -v Three tests pass. Delete with correct password returns 204. Delete with wrong password returns 403 and user survives. Delete with no body returns 422. Task 2: Frontend — adminDeleteUser API function + inline delete confirmation panel frontend/src/api/client.js, frontend/src/components/admin/AdminUsersTab.vue ### 1. client.js — add adminDeleteUser Export `adminDeleteUser(id, adminPassword)`: ```javascript export function adminDeleteUser(id, adminPassword) { return request(`/api/admin/users/${id}`, { method: 'DELETE', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ admin_password: adminPassword }), }) } ``` ### 2. AdminUsersTab.vue — add delete confirmation state In `