Commit Graph
78 Commits
Author SHA1 Message Date
curo1305 6d1c02f703 refactor(09-05): CODE-09 purge — Phase 8 backend sub-packages (WHAT comments removed)
- api/admin/users.py: removed module docstring + 7 WHAT function docstrings
- api/admin/quotas.py: removed module docstring + 2 WHAT function docstrings
- api/admin/ai.py: removed module docstring + 3 WHAT inline comments; security invariant docstrings preserved
- api/admin/shared.py: unchanged (all comments are WHY — constraint notes)
- api/documents/upload.py: removed 4 WHAT inline comments; T-03-05/T-03-06 WHY notes preserved
- api/documents/content.py: removed WHAT function docstring + WHAT inline comment
- api/documents/crud.py: removed 7 WHAT inline comments; D-16 + security constraint docstrings preserved
- api/auth/shared.py: removed module docstring + 1 WHAT inline comment
- api/auth/tokens.py: removed module docstring + 8 WHAT function docstrings/comments; family-revocation + SEC-02 WHY notes preserved
- api/auth/totp.py: removed module docstring + 2 WHAT function docstrings + 2 WHAT inline comments
- api/auth/password.py: removed module docstring + 2 WHAT function docstrings + 3 WHAT inline comments
- All NO-prefix anchor comments and security-invariant WHY comments preserved
- pytest: 413 passed, 1 failed (pre-existing ModuleNotFoundError unrelated to purge)
2026-06-12 16:59:46 +02:00
curo1305 41d81c089f feat(09-01): implement GET /api/admin/overview endpoint — ADMIN-11
- New backend/api/admin/overview.py: aggregate stats (user_count,
  total_storage_bytes, doc_status) + last 10 audit entries in one payload
- Sub-router carries NO prefix (parent __init__.py carries /api/admin)
- Reuses _build_filtered_query_with_handles + _audit_to_dict_with_handles
  from api.audit — no new serializer or query logic
- Registered overview_router on admin parent router in __init__.py
- All 8 ADMIN-11 tests pass; existing admin and audit suites stay green
2026-06-12 15:50:38 +02:00
curo1305andClaude Sonnet 4.6 81337bd9e3 feat(08-05): decompose documents API monolith into documents/ package — CODE-02 CODE-08
- upload.py: request_upload_url, upload_document, confirm_upload (3 routes)
- crud.py: list_documents, get_document, patch_document, delete_document, classify_document (5 routes)
- content.py: stream_document_content + _parse_range (1 route)
- shared.py: _CLOUD_PROVIDERS, UploadUrlRequest, DocumentPatch (single definitions)
- __init__.py: router aggregator prefix=/api/documents, 9 routes total
- documents.py monolith deleted; all URL paths unchanged
- list_documents registered on parent router to avoid FastAPI empty-path/prefix check
- get_storage_backend_for_document re-exported for test monkeypatching compatibility

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-12 10:27:48 +02:00
curo1305andClaude Sonnet 4.6 f01fb0e6d5 feat(08-04): delete admin.py monolith after URL regression — CODE-01 CODE-08
Task 3: 54 admin+cloud+ai-config tests pass with package; admin.py and
admin_OLD_REMOVE_IN_TASK_3.py removed. admin/ package is the sole definition.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-12 10:16:38 +02:00
curo1305 3ec198768d feat(08-04): create backend/api/admin/ package — users, quotas, ai, shared, init
- users.py: list_users, create_user, update_user_status, initiate_password_reset,
  update_ai_config, delete_user, create_system_topic (7 routes)
- quotas.py: get_user_quota, update_user_quota (2 routes)
- ai.py: get_ai_config_models, test_ai_connection, get_ai_config,
  update_system_ai_config (4 routes); validate_provider_id call-through (D-11)
- shared.py: _user_to_dict helper (T-02-27 / SEC-07 field whitelist)
- __init__.py: router aggregator with prefix=/api/admin; 13 routes total
- admin.py monolith renamed to admin_OLD_REMOVE_IN_TASK_3.py (deleted in task 3)
- All sub-routers have NO prefix (D-04)
- All handlers inject Depends(get_current_admin) (T-08-04-01)
2026-06-10 18:43:01 +02:00
curo1305 5117e2542a feat(08-06): create backend/api/auth/ package — Task 1
- Create api/auth/shared.py: limiter, all Pydantic request models, _set_refresh_cookie, _user_dict
- Create api/auth/tokens.py: register, login, refresh, logout, logout-all, me, quota, preferences handlers
- Create api/auth/totp.py: totp/setup, totp/enable, totp DELETE handlers (CR-02, CR-03 preserved)
- Create api/auth/password.py: change-password, password-reset, password-reset/confirm handlers (CR-01 preserved)
- Create api/auth/__init__.py: router aggregator with prefix=/api/auth; re-exports limiter
- Rename auth.py monolith to auth_OLD_REMOVE_IN_TASK_2.py (deleted in Task 2 after tests pass)
- All 15 routes confirmed; limiter re-export verified; skip_token_hash calls preserved verbatim
2026-06-10 18:42:27 +02:00
curo1305 61fa6e2051 refactor(08-02): switch cloud.py import to api.schemas.CloudConnectionOut
- Replace `from api.admin import CloudConnectionOut` with `from api.schemas import CloudConnectionOut`
- Eliminates cross-package coupling between api/cloud.py and api/admin.py (Pitfall 3)
- All 51 cloud + admin tests pass; CloudConnectionOut serialization unchanged
- Original definition in api/admin.py untouched until plan 08-04
2026-06-08 16:12:38 +02:00
curo1305 10e0900a89 feat(08-02): create backend/api/schemas.py with CloudConnectionOut
- New cross-package Pydantic schemas module (D-10, CODE-08)
- CloudConnectionOut moved here from api/admin.py to eliminate coupling
- SEC-08: credentials_enc deliberately excluded; 7-field whitelist preserved
- field_validator coerce_id_to_str preserves UUID→str coercion behavior
- Original definition in api/admin.py left intact until plan 08-04
2026-06-08 16:11:58 +02:00
curo1305 61b1e045c4 feat(07.4-02): update api/auth.py call sites, promote FGP tests, version bump 0.1.3
- api/auth.py login + refresh call sites pass User-Agent and Accept-Language
  headers to create_access_token (D-05 — fgp binding at token issuance)
- test_auth_fgp.py: promote all 4 xfail stubs to real assertions (FGP-01..04)
- conftest.py: add _TEST_USER_AGENT constant; configure async_client to send
  consistent User-Agent; update auth_user/second_auth_user/admin_user fixtures
  to bind fgp to _TEST_USER_AGENT so tokens validate correctly in tests
- test_auth_deps.py: import _TEST_USER_AGENT; update auth_client fixture and
  all create_access_token calls to use the constant
- test_cloud.py: update _create_user_and_token to bind fgp to _TEST_USER_AGENT
- test_documents.py: update 3 inline create_access_token calls to pass user_agent
- test_security_headers.py: import _TEST_USER_AGENT; update headers_client +
  token creation to use the constant
- Version bump: backend 0.1.2 → 0.1.3, frontend 0.1.2 → 0.1.3
- [Rule 1 - Bug] Fix httpx default User-Agent vs empty-string fgp mismatch in
  test infrastructure: 10 tests were failing due to fgp check rejecting tokens
  created with fgp="" when client sent "python-httpx/X.Y.Z"
2026-06-06 22:12:57 +02:00
curo1305 9cc11b5446 feat(07.3-03): backend remember_me — TTL split + cookie Max-Age + 3 promoted tests
- services/auth.py: create_refresh_token gains remember_me=False param; selects 16h or 30d TTL (D-09, D-10, D-11)
- api/auth.py: LoginRequest.remember_me bool field; _set_refresh_cookie remember_me param for conditional max_age; login handler threads remember_me through both calls (D-11, RM-03)
- test_auth_es256.py: promote RM-01, RM-02, RM-03 stubs — all 9 phase tests now PASSED
2026-06-06 17:21:14 +02:00
curo1305 d3deef4f95 fix(07.2): add user_nbf write to password_reset_confirm — CR-02 gap closure 2026-06-06 00:11:41 +02:00
curo1305 ae11a6e913 security(07.2-03): add import time + user_nbf write to admin deactivation handler; promote stubs to PASSED 2026-06-06 00:02:27 +02:00
curo1305 efeb75b279 feat(07.2-03): add import time + user_nbf writes to api/auth.py (change_password, enable_totp, disable_totp) 2026-06-06 00:01:19 +02:00
curo1305andClaude Sonnet 4.6 c38c6b1c01 feat(07.1): session revocation on privilege change — CR-01/CR-02/CR-03
- revoke_all_refresh_tokens: add skip_token_hash optional param (exclude
  current session while revoking others)
- change_password, enable_totp, disable_totp: call revoke with skip hash
  derived from refresh cookie; return sessions_revoked in response and
  write to audit log metadata_
- 3 new tests: test_{change_password,enable_totp,disable_totp}_revokes_other_sessions
  — all PASSED; 373 total passing, 0 regressions
- Frontend toasts: SettingsAccountTab + TotpEnrollment show
  "Other sessions have been terminated." when sessions_revoked > 0
- Companion fixes: rate_limiting get_client_ip refactor, deps/auth.py
  request.state.current_user, locustfile refresh-token task removal
- Version bump: 0.1.0 → 0.1.1

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-05 12:47:16 +02:00
curo1305andClaude Sonnet 4.6 0fa23f5211 fix(auth): catch IntegrityError on register race → 409 + locustfile pre-auth rewrite
Two fixes from manual SLA validation (06-LOCUST-01):

1. backend/api/auth.py: concurrent registrations with same email/handle caused
   UniqueViolation to bubble as 500. Now wraps session.commit() in try/except
   IntegrityError → 409 Conflict. Regression covered by existing auth API tests.

2. backend/load_tests/locustfile.py: rewrote from single-shared-account to
   pre-authenticated unique accounts. New @events.test_start listener creates
   50 accounts in 9-user batches (65 s pause between batches to stay under
   10 req/min IP rate limit). Added min-request guard to SLA check so a
   run-time-consumed-by-setup result never silently "passes".
   Run command updated to --run-time 12m.

Known open issue: _account_key in services/rate_limiting.py falls back to IP
when request.state.current_user is not yet set (rate limiter key_func runs
before handler body). Fix: extract sub from JWT directly. See next session.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-05 12:25:04 +02:00
curo1305andClaude Sonnet 4.6 3b11b9a596 feat(07-uat): admin AI panel UX enhancements + DocumentCard status indicators
- GET /api/admin/ai-config/models — fetch model list from provider's /models endpoint
- POST /api/admin/ai-config/test-connection — replaced GET; accepts unsaved form
  values (api_key, base_url, model_name) so admins can test before saving
- TestConnectionRequest Pydantic model with provider_id validation
- SearchableModelSelect.vue — new reusable combobox; Teleport to body avoids
  overflow:hidden clipping; shows all models on open, filters only on typing;
  static "Enter manually" item always pinned at bottom; caches per provider
- AdminAiConfigTab: model name input replaced with SearchableModelSelect;
  testingProvider ref + spinner added to Test Connection button
- DocumentCard: processing spinner + "Classifying…", pending "Queued" badge,
  classification_failed badge; topics section only shown when status=ready
- FileManagerView: onUnmounted imported (polling wiring pending next session)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-05 09:26:02 +02:00
curo1305 e678930b8d feat(07-05): admin AI-config endpoints + load_provider_config_by_id + atomic is_active flip
- Add GET/PUT /api/admin/ai-config + GET /api/admin/ai-config/test-connection
- _ai_config_to_dict whitelist excludes api_key_enc (T-07-01)
- PUT: HKDF-encrypt api_key, atomic UPDATE SET is_active = (provider_id = target) (T-07-03)
- PUT: upsert with PROVIDER_DEFAULTS for omitted fields; audit log records fields_changed only (T-07-14)
- All 3 endpoints require get_current_admin (T-07-15)
- Add load_provider_config_by_id to services/ai_config.py (ignores is_active)
- Promote 3 xfail tests in test_admin_ai_config.py — all 3 pass
2026-06-04 23:19:40 +02:00
curo1305andClaude Sonnet 4.6 7cd29e9454 fix(06): WR-07 replace print() with structlog for cloud delete errors
Replace print(..., file=sys.stderr) with structlog.get_logger(__name__)
.warning("cloud_delete_failed", ...) so cloud delete errors carry a
correlation ID, are picked up by promtail, and appear in Loki — matching
the Phase 6 goal of routing all logging through structlog.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-04 23:15:49 +02:00
curo1305andClaude Sonnet 4.6 10970d9557 fix(06): CR-07 validate event_type against allowlist before LIKE filter
Add _VALID_EVENT_PREFIXES frozenset and validate event_type at all three
filter sites in audit.py (_build_filtered_query, _build_filtered_query
_with_handles, and the inline count query in list_audit_log). Invalid
values return HTTP 422. Also change like() pattern from prefix% to
prefix.% to enforce true prefix-match semantics.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-04 23:15:48 +02:00
curo1305andClaude Sonnet 4.6 aad7635623 fix(06): CR-05 hash attempted email in audit log to avoid PII storage
Replace raw email in auth.login_failed metadata_ with a 16-char
SHA-256 prefix (attempted_email_hash). Update AuditLogTab.vue to
display the hash field instead. This removes plaintext email PII from
the unencrypted audit_logs JSONB column as required by CLAUDE.md.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-04 23:15:48 +02:00
curo1305andClaude Sonnet 4.6 3a6251ca23 fix(06): CR-04 add allowlist validation for default_storage_backend
Add _VALID_BACKENDS frozenset and validate body.backend before writing
to DB in PATCH /api/users/me/default-storage. Returns HTTP 422 for
any value not in the allowlist, preventing mass-assignment and logic
bypass via arbitrary string values.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-04 23:15:48 +02:00
curo1305andClaude Sonnet 4.6 23c27efd65 fix(06): CR-03 replace raw X-Forwarded-For reads with get_client_ip()
Replace all instances of request.headers.get("X-Forwarded-For") in
cloud.py (connect_webdav, delete_connection) and documents.py (upload,
confirm, delete) with get_client_ip(request) from deps.utils. Add
get_client_ip import to both files. Prevents XFF spoofing in audit logs.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-04 23:15:48 +02:00
curo1305 63cd707d52 feat(07-04): POST /classify re-queues Celery, removes synchronous classify (D-11)
- Replace synchronous await classifier.classify_document() with extract_and_classify.delay()
- Set doc.status='processing' and commit before dispatching Celery task
- Return {'document_id': str, 'status': 'processing'} instead of assigned topics
- Promote test_reclassify_requeues_celery from xfail stub to passing test
- Add test_reclassify_cross_user_returns_404 for IDOR security invariant (T-07-10)
2026-06-04 23:10:49 +02:00
curo1305andClaude Sonnet 4.6 a826738e18 feat(06-05): trusted-proxy get_client_ip, per-account rate limiter, promote 8 xfail tests (D-11/D-12/D-13)
- D-11: replace get_client_ip body with trusted-proxy CIDR check (127/8, 172.16/12,
  192.168/16, ::1/128); untrusted peers always return their own IP, preventing XFF
  spoofing by external callers
- D-12: create services/rate_limiting.py with _account_key() keyed by user.id
  (falls back to peer IP when no authenticated user in request.state); exports
  account_limiter = Limiter(key_func=_account_key)
- Wire: auth.py switches from get_remote_address to get_client_ip; main.py imports
  account_limiter; all 9 documents.py endpoints and 7 cloud.py endpoints decorated
  @account_limiter.limit("100/minute") with request.state.current_user = current_user
  as the first handler statement (A1 ordering invariant)
- Promote all 8 xfail stubs to real assertions; add autouse fixture in conftest.py
  to reset MemoryStorage between tests preventing cross-test 429 contamination

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-04 18:27:49 +02:00
curo1305andClaude Sonnet 4.6 a548266461 refactor(backend): extract shared helper modules per architecture rules
- Add backend/ai/utils.py — parse_classification, parse_suggestions, strip_code_fences
  shared by all AI providers; removes duplicated private functions from
  anthropic_provider.py and openai_provider.py
- Add backend/deps/utils.py — get_client_ip, parse_uuid request-parsing helpers;
  removes local _ip() variants from admin.py, auth.py, shares.py, folders.py
- Add backend/storage/exceptions.py — canonical CloudConnectionError definition;
  all routers and backends import from here instead of redefining
- Move validate_password_strength to backend/services/auth.py; removes duplicated
  _validate_password_strength from admin.py and auth.py

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-02 16:10:35 +02:00
curo1305andClaude Sonnet 4.6 2686fde2d7 feat(06.2): log attempted email on failed login and surface it in audit log
- auth.py: store attempted_email in metadata_ and link user_id when the account exists (wrong password case); previously logged no PII at all
- AuditLogTab: Email column falls back to metadata_.attempted_email in amber with "(attempted)" label when no confirmed user_email is available

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-01 21:02:37 +02:00
curo1305andClaude Sonnet 4.6 7027347597 fix(06.2): audit log — add email column, remove @ prefix from handles
- Backend: add user_email to _build_filtered_query_with_handles (UserSubject join) and _audit_to_dict_with_handles; propagate through JSON viewer and CSV export including empty-result path
- Frontend: AuditLogTab adds Email column between User and Action Type; removes @ prefix from handle cell
- Test: update expected CSV header to include user_email

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-01 20:59:09 +02:00
curo1305andClaude Sonnet 4.6 d771f0805d fix(06.2): shared badge and recipient handle missing in FileManagerView
- shares.py grant_share: include recipient_handle in response so ShareModal shows the name immediately without reload
- FileManagerView: add Shared pill badge next to document name (badge only existed in DocumentCard, not the main file manager view)
- FileManagerView ShareModal: wire @unshared to clear is_shared flag when last recipient is removed

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-01 19:47:00 +02:00
curo1305andClaude Sonnet 4.6 089da94d8b fix(security): apply two findings from sharing security review
- get_document: strip extracted_text for share recipients (T-04-04-03 consistency)
- ShareModal: emit 'unshared' when last recipient is revoked; DocumentCard clears is_shared badge

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-01 19:41:15 +02:00
curo1305andClaude Sonnet 4.6 a0f6c2f663 fix(06.2): resolve four sharing UX issues found in re-test UAT
- AccountView: remove hardcoded @ prefix so handle matches what share dialog expects
- documents store: set is_shared=true optimistically after successful share so badge shows without refetch
- GET /api/documents/{id}: allow recipients of an active share to view the document (was returning 404 for non-owners)
- ShareModal: move Share button to its own full-width row so it no longer overflows the input area

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-01 19:32:51 +02:00
curo1305andClaude Sonnet 4.6 b245fcc527 fix(phase-03): use UUID.hex in raw SQL to fix SQLite UUID format mismatch
str(uuid_obj) produces dashed 36-char format; SQLite stores UUID as 32-char
hex without dashes, so WHERE user_id = :uid never matched. Using .hex fixes
confirm_upload (api/documents.py) and delete_document (services/storage.py).
Removes stale xfail from test_delete_decrements_quota — now passes on SQLite.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-01 15:43:01 +02:00
curo1305 683670afa1 fix(06.2): WR-02 constrain format parameter to Literal[csv] to reject unsupported formats 2026-06-01 14:31:32 +02:00
curo1305 2072c3ddcd fix(06.2): WR-08 delete_document defers commit so audit log writes in same transaction 2026-06-01 14:30:31 +02:00
curo1305 50b6e7fd06 fix(06.2): WR-07 document X-Forwarded-For trust boundary in all IP extraction code 2026-06-01 14:29:35 +02:00
curo1305 1f2cec9ac3 fix(06.2): CR-07 add audit log entry for PATCH /shares/{share_id} permission change 2026-06-01 14:27:08 +02:00
curo1305 1a34209bb0 fix(06.2): CR-06 RFC 5987-encode Content-Disposition filename to prevent header injection 2026-06-01 14:26:46 +02:00
curo1305 653cb3a98b fix(06.2): CR-05 remove UUID dash-stripping in quota SQL — PostgreSQL expects dashed UUID format 2026-06-01 14:26:24 +02:00
curo1305 792d4639d1 fix(06.2): CR-03 serialize metadata_ with json.dumps in CSV export instead of Python repr 2026-06-01 14:25:29 +02:00
curo1305 50859bb430 fix(06.2): CR-02 add MinIOBackend guard in download_daily_export before accessing _client 2026-06-01 14:25:06 +02:00
curo1305 a3ad36cc82 fix(06.2): CR-01 event-type filter uses prefix LIKE match instead of exact equality 2026-06-01 14:24:50 +02:00
curo1305 46f7505e36 chore: merge executor worktree (worktree-agent-af66944050628b0e4) 2026-05-31 15:23:36 +02:00
curo1305andClaude Sonnet 4.6 62daf0d750 test(phase-04): fill Nyquist validation gaps — FOLD-04, FOLD-05, SEC-08, SEC-09
Add 6 new tests covering document sort (name/size), FTS search cross-user
isolation, credentials_enc exclusion from all responses, and MinIO object
cleanup on user deletion.

Fix FTS try/except misplacement in api/documents.py — was wrapping the ORM
statement builder (never raises) instead of the execute call, causing HTTP 500
on SQLite test env. Now falls back to unfiltered results when @@ unsupported.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-31 15:21:02 +02:00
curo1305 839bfe0ffe feat(06.2-04): backend — handle enrichment, user_handle filter, two daily-export endpoints
- Add _audit_to_dict_with_handles() with user_handle + actor_handle fields
- Add _build_filtered_query_with_handles() with aliased User double-JOIN
- Change list_audit_log user_id param to user_handle string with handle→UUID resolution
- Change export_audit_log user_id param to user_handle (Pitfall 7 — both endpoints enriched)
- Add GET /audit-log/daily-exports — lists MinIO audit-logs bucket, asyncio.to_thread
- Add GET /audit-log/daily-exports/{date} — streams CSV, date regex validation (T-06.2-04-01)
- Move daily-export endpoints before viewer to ensure specific path registration order
- Update test_audit_log_export_csv to match enriched CSV header (user_handle, actor_handle)
- All 10 test_audit.py tests pass
2026-05-31 15:17:53 +02:00
curo1305andClaude Sonnet 4.6 95c7ed786a feat(06.2-03): backend — cloud-aware delete routing + skip_quota + remove_only param
- storage.delete_document gains skip_quota=False param; quota decrement gated on it
- DELETE /api/documents/{id} gains remove_only=bool query param
- Cloud docs (storage_backend != minio): attempt cloud backend delete_object first
  - On failure: return HTTP 200 {success: false, cloud_delete_failed: true} (not 4xx)
  - On success or remove_only: delete DB row with skip_quota=True
- Cloud creds/exception message never included in response body (T-06.2-03-02)
- Promote 3 xfail stubs to real tests (propagates, failure, remove_only)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-31 15:09:44 +02:00
curo1305andClaude Sonnet 4.6 ea231853e9 feat(06.2-02): backend — ShareCreate.permission field + PATCH /{share_id} endpoint
- Add permission field (default "view") with field_validator to ShareCreate
- Add SharePermissionPatch model with same validator
- Wire body.permission into grant_share() Share constructor
- Add PATCH /{share_id} endpoint with IDOR protection (T-06.2-02-01)
- Promote 3 xfail stubs to real tests (create_with_permission, patch_permission, patch_idor)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-31 15:04:53 +02:00
curo1305andClaude Sonnet 4.6 7be48266ae docs(06.2): capture phase context + fix admin user creation 500
- Phase 6.2 CONTEXT.md: cloud-delete propagation, SHARE-03/05, audit
  log CSV export fix, daily export UI, user handle display
- Fix: admin create_user missing session.flush() before write_audit_log
  caused FK violation on PostgreSQL (silent on SQLite)
- Regression test: test_create_user_writes_audit_log in test_admin_api.py

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-31 11:00:45 +02:00
curo1305andClaude Sonnet 4.6 bf7d86184d fix(documents): normalize UUID to undashed hex in raw SQL quota UPDATE
str(uuid) returns dashed format (xxxx-xxxx-…) which mismatches SQLite's
CHAR(32) storage (undashed hex). Replace with .replace('-', '') so the
WHERE clause matches in both SQLite (tests) and PostgreSQL (production).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-30 18:57:02 +02:00
curo1305andClaude Sonnet 4.6 b1a136b5be fix(05-12): resolve 3 critical code review findings
CR-01: add `except HTTPException: raise` before broad except in
stream_document_content — prevents 503 (reconnect prompt) from being
swallowed and replaced with misleading 502

CR-02: move pre-flight credential checks BEFORE Redis setex in
oauth_initiate — no orphan state tokens written for unconfigured providers;
also adds onedrive_tenant_id to OneDrive pre-flight condition (WR-02)

CR-03: add CLOUD_CREDS_KEY to celery-worker environment in docker-compose.yml
— worker cannot decrypt cloud credentials without this key; every cloud
document task was silently failing at runtime

WR-03: assert Redis store empty after 400 pre-flight responses in both
new tests — confirms no token leak on misconfigured-provider requests

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-30 18:04:09 +02:00
curo1305andClaude Sonnet 4.6 10175ee4b5 fix(05-12): close 3 UAT gaps — OAuth 400 preflight, 502 cloud fallback, upload hint
- oauth_initiate: pre-flight check returns 400 with env-var hint when
  GOOGLE_CLIENT_ID/SECRET or ONEDRIVE_CLIENT_ID/SECRET are not configured,
  preventing opaque MSAL/OAuth library 500 errors on misconfigured servers
- stream_document_content: broad except-clause catches non-CloudConnectionError
  exceptions and returns 502 with user-friendly message (was raw 500)
- docker-compose.yml: add volumes: - ./backend:/app to celery-worker so code
  changes are picked up by docker compose restart without a rebuild
- CloudStorageView: upload hint paragraph directs users to navigate into a
  cloud folder; no DropZone added (no folder context at overview level)
- 3 new backend tests pass; 2 existing tests patched with credential monkeypatch;
  full suite: 293 passed, 0 new failures, 1 pre-existing (test_extract_docx)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-30 17:55:08 +02:00
curo1305andClaude Sonnet 4.6 54ef3357ba fix(05): cloud API path param, root sentinel, webdav creds in list, upload path
cloud.py: list_connections now decrypts and surfaces server_url +
connection_username for nextcloud/webdav providers; folder route uses
{folder_id:path} to handle slashes; translates "root" sentinel to "".
nextcloud_backend.py: skip parent directory entry in PROPFIND Depth:1 results.
webdav_backend.py: add cloud_folder + original_filename params to
upload_object so files land in the user's chosen folder with their real name.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-30 11:58:01 +02:00