feat(05-11): add UserDeleteConfirm model + admin password verification in delete_user

- Import verify_password from services.auth
- Add UserDeleteConfirm Pydantic model (admin_password field)
- delete_user handler now requires body; fails fast with 403 on wrong password
- All existing SEC-09 cloud/MinIO purge logic and audit log unchanged
- Three new tests pass: 204 on correct pw, 403 on wrong pw, 422 on no body
This commit is contained in:
curo1305
2026-05-30 11:37:59 +02:00
parent 8727592bff
commit 390a693ec6
+17 -1
View File
@@ -37,7 +37,7 @@ from db.models import CloudConnection, Document, Quota, RefreshToken, Topic, Use
from deps.auth import get_current_admin
from deps.db import get_db
from services.audit import write_audit_log
from services.auth import hash_password, revoke_all_refresh_tokens
from services.auth import hash_password, revoke_all_refresh_tokens, verify_password
from storage import get_storage_backend, get_storage_backend_for_document
router = APIRouter(prefix="/api/admin", tags=["admin"])
@@ -138,6 +138,12 @@ class SystemTopicCreate(BaseModel):
color: str = "#6366f1"
class UserDeleteConfirm(BaseModel):
"""Admin password confirmation required before hard-deleting a user (ADMIN-02, T-05-11-01)."""
admin_password: str
# ── SEC-08: Safe CloudConnection response model ───────────────────────────────
class CloudConnectionOut(BaseModel):
@@ -472,6 +478,7 @@ async def update_ai_config(
@router.delete("/users/{user_id}", status_code=status.HTTP_204_NO_CONTENT)
async def delete_user(
user_id: uuid.UUID,
body: UserDeleteConfirm,
request: Request,
session: AsyncSession = Depends(get_db),
_admin: User = Depends(get_current_admin),
@@ -479,11 +486,20 @@ async def delete_user(
"""Delete a user account and clean up all their MinIO objects (SEC-09, D-19).
Security invariants:
- Admin password verified via Argon2 before any deletion (T-05-11-01)
- Cannot delete admin accounts (T-04-07-04)
- MinIO objects are deleted BEFORE DB records are removed (SEC-09)
- MinIO deletion is best-effort (try/except) — DB row is deleted regardless
- Audit log written with event_type="admin.user_deleted"
"""
# T-05-11-01: Verify admin password before performing any destructive action.
# Fail fast — no DB reads for the target user until the admin is confirmed.
if not verify_password(body.admin_password, _admin.password_hash):
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail="Invalid admin password",
)
user = await session.get(User, user_id)
if user is None:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="User not found")