Files

6.9 KiB

phase, plan, subsystem, tags, dependency_graph, tech_stack, key_files, decisions, metrics
phase plan subsystem tags dependency_graph tech_stack key_files decisions metrics
04-folders-sharing-quotas-document-ux 07 security-hardening
audit-log
sec-08
sec-09
d-13
admin
requires provides affects
04-05
04-06
audit-log-backfill-auth
audit-log-backfill-documents
audit-log-backfill-admin
sec-08-credentials-enc-exclusion
sec-09-delete-user-minio-cleanup
backend/api/auth.py
backend/api/admin.py
backend/api/documents.py
added patterns
write_audit_log() flush-before-commit pattern (D-14): audit entry flushed in same transaction as primary operation
SEC-08 CloudConnectionOut whitelist pattern: Pydantic response model excludes credentials_enc by omission
SEC-09 pre-delete MinIO cleanup: collect docs, delete_object best-effort, then session.delete(user)
created modified
backend/api/auth.py
backend/api/admin.py
backend/api/documents.py
backend/tests/test_admin_api.py
write_audit_log for login_failed uses metadata_=None (no PII) — T-04-07-01
document.uploaded and document.deleted audit metadata contains only size_bytes and storage_backend — T-04-07-02
audit write for login success written just before return, inside a session.commit() call since create_refresh_token already flushed
delete_document audit log written after storage.delete_document commits, in a new flush + commit — storage service owns its own commit
admin.user_deleted audit written with session.flush() before session.delete(user) to keep user FK intact
test_admin_impersonation_not_found updated to accept 405 in addition to 404/422 (Rule 1 auto-fix)
duration completed_at tasks_completed files_modified
~25 minutes 2026-05-25T19:51:37Z 2 4

Phase 4 Plan 7: Audit Log Backfill, SEC-08 CloudConnectionOut, SEC-09 Delete-User Summary

One-liner

Audit log backfill for all D-13 event categories (auth, document, admin), SEC-08 credentials_enc exclusion via CloudConnectionOut Pydantic model, SEC-09 MinIO object cleanup in delete-user endpoint.

What Was Built

Task 1: Audit Log Backfill — auth.py and documents.py

Added from services.audit import write_audit_log to both files and backfilled write_audit_log calls into 8 auth handlers and 2 document handlers:

auth.py (9 write_audit_log calls):

  • auth.login_failed — login with wrong credentials; metadata_=None (no PII, T-04-07-01)
  • auth.login — successful login; metadata_={"totp_used": bool}
  • auth.backup_code_used — backup code consumed during login
  • auth.logout — refresh token revoked; user_id extracted from RefreshToken row
  • auth.sign_out_all — all sessions revoked; metadata_={"sessions_revoked": count}
  • auth.password_changed — password updated by user
  • auth.totp_enrolled — TOTP enabled and backup codes generated
  • auth.totp_revoked — TOTP disabled

Added request: Request parameter to change_password and disable_totp (previously missing).

documents.py (2 write_audit_log calls):

  • document.uploaded — upload confirmed; metadata_={"size_bytes": N, "storage_backend": "minio"} — NO filename, NO extracted_text (T-04-07-02)
  • document.deleted — document deleted; metadata_={"size_bytes": N}

Added request: Request parameter to confirm_upload and delete_document.

Task 2: SEC-08, SEC-09, Admin Audit Writes — admin.py

CloudConnectionOut (SEC-08):

class CloudConnectionOut(BaseModel):
    """SEC-08: credentials_enc deliberately excluded."""
    id: str
    provider: str
    display_name: str
    status: str
    connected_at: datetime
    model_config = {"from_attributes": True}

Model placed in admin.py for Phase 5 import-ready use. credentials_enc absent by whitelist.

Admin audit log writes (5 additional write_audit_log calls):

  • admin.user_created — new user created by admin
  • admin.user_deactivated — user deactivated
  • admin.user_activated — user reactivated
  • admin.quota_changed — metadata_={"old_bytes": N, "new_bytes": M}
  • admin.ai_provider_assigned — metadata_={"provider": ..., "model": ...}

DELETE /api/admin/users/{user_id} (SEC-09):

  • Auth: get_current_admin
  • Guard: 400 if target user is admin (T-04-07-04)
  • Collects all Document rows for user via select(Document).where(Document.user_id == user_id)
  • Calls get_storage_backend().delete_object(doc.object_key) for each — try/except (best-effort)
  • Writes admin.user_deleted audit log via session.flush() BEFORE session.delete(user)
  • Cascades delete to quota, documents, refresh_tokens via FK ON DELETE CASCADE
  • Returns 204 No Content

Deviations from Plan

Auto-fixed Issues

1. [Rule 1 - Bug] test_admin_impersonation_not_found: 405 not accepted

  • Found during: Task 2 test run
  • Issue: Adding DELETE /api/admin/users/{user_id} caused FastAPI to return 405 Method Not Allowed for GET requests to /api/admin/users/impersonate (previously 422 UUID parse error). The test only accepted {404, 422}.
  • Fix: Updated assertion to accept 405 in addition to 404/422. Security invariant is still met: there is no GET impersonation endpoint. 405 means the path is handled but only via DELETE — which is the user-delete endpoint, not impersonation.
  • Files modified: backend/tests/test_admin_api.py
  • Commit: 8e6005c

Threat Model Coverage

Threat ID Status
T-04-07-01 Mitigated: login_failed metadata_=None, no PII
T-04-07-02 Mitigated: document audit contains only size_bytes and storage_backend
T-04-07-03 Mitigated: CloudConnectionOut model excludes credentials_enc
T-04-07-04 Mitigated: DELETE /users/{id} returns 400 for admin accounts
T-04-07-05 Mitigated: MinIO objects deleted before DB record in delete-user
T-04-07-06 Mitigated: 8 auth event types + 2 doc events + 5 admin events logged

Verification Results

  • pytest tests/test_auth_api.py tests/test_documents.py -x -v — 43 passed, 5 xfailed
  • pytest tests/test_admin_api.py tests/test_security.py -x -v — 18 passed, 2 xfailed
  • pytest tests/ -x — 92 passed, 1 failed (pre-existing test_extract_docx missing python-docx), 3 skipped, 10 xfailed
  • grep -c "write_audit_log" backend/api/auth.py → 9 (>= 6 required)
  • grep -c "write_audit_log" backend/api/documents.py → 3 (>= 2 required)
  • grep -c "write_audit_log" backend/api/admin.py → 6 (>= 4 required)
  • No email/password in login_failed metadata confirmed by grep
  • No filename/extracted_text in document audit calls confirmed by grep

Self-Check: PASSED

  • backend/api/auth.py exists and contains write_audit_log import and 9 calls
  • backend/api/admin.py exists and contains CloudConnectionOut, delete_user endpoint, 6 write_audit_log calls
  • backend/api/documents.py exists and contains write_audit_log import and 3 calls (import + 2 calls)
  • Commits e451b16 and 8e6005c exist in git log