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:
+17
-1
@@ -37,7 +37,7 @@ from db.models import CloudConnection, Document, Quota, RefreshToken, Topic, Use
|
|||||||
from deps.auth import get_current_admin
|
from deps.auth import get_current_admin
|
||||||
from deps.db import get_db
|
from deps.db import get_db
|
||||||
from services.audit import write_audit_log
|
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
|
from storage import get_storage_backend, get_storage_backend_for_document
|
||||||
|
|
||||||
router = APIRouter(prefix="/api/admin", tags=["admin"])
|
router = APIRouter(prefix="/api/admin", tags=["admin"])
|
||||||
@@ -138,6 +138,12 @@ class SystemTopicCreate(BaseModel):
|
|||||||
color: str = "#6366f1"
|
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 ───────────────────────────────
|
# ── SEC-08: Safe CloudConnection response model ───────────────────────────────
|
||||||
|
|
||||||
class CloudConnectionOut(BaseModel):
|
class CloudConnectionOut(BaseModel):
|
||||||
@@ -472,6 +478,7 @@ async def update_ai_config(
|
|||||||
@router.delete("/users/{user_id}", status_code=status.HTTP_204_NO_CONTENT)
|
@router.delete("/users/{user_id}", status_code=status.HTTP_204_NO_CONTENT)
|
||||||
async def delete_user(
|
async def delete_user(
|
||||||
user_id: uuid.UUID,
|
user_id: uuid.UUID,
|
||||||
|
body: UserDeleteConfirm,
|
||||||
request: Request,
|
request: Request,
|
||||||
session: AsyncSession = Depends(get_db),
|
session: AsyncSession = Depends(get_db),
|
||||||
_admin: User = Depends(get_current_admin),
|
_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).
|
"""Delete a user account and clean up all their MinIO objects (SEC-09, D-19).
|
||||||
|
|
||||||
Security invariants:
|
Security invariants:
|
||||||
|
- Admin password verified via Argon2 before any deletion (T-05-11-01)
|
||||||
- Cannot delete admin accounts (T-04-07-04)
|
- Cannot delete admin accounts (T-04-07-04)
|
||||||
- MinIO objects are deleted BEFORE DB records are removed (SEC-09)
|
- MinIO objects are deleted BEFORE DB records are removed (SEC-09)
|
||||||
- MinIO deletion is best-effort (try/except) — DB row is deleted regardless
|
- MinIO deletion is best-effort (try/except) — DB row is deleted regardless
|
||||||
- Audit log written with event_type="admin.user_deleted"
|
- 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)
|
user = await session.get(User, user_id)
|
||||||
if user is None:
|
if user is None:
|
||||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="User not found")
|
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="User not found")
|
||||||
|
|||||||
Reference in New Issue
Block a user