Commit Graph

8 Commits

Author SHA1 Message Date
Brummel 813fb896ff refactor: consolidate bulk actions, URL assembly, and case-artefact filenames
Pulls three pattern groups into shared helpers so a rename or wire-format
tweak edits one file instead of 10+:

- BulkAction enum in doctate-common replaces the "close"/"analyze"/"reset"
  string literals that were duplicated between the bulk handler and 3
  attack/CSRF test files. FromStr preserves the exact "Unbekannte Aktion"
  error shape; the handler match is now exhaustive over the enum.
- join_url helper in doctate-common absorbs 7 identical
  trim_end_matches+format! call sites across client-core, client-desktop,
  server/transcribe (ollama, whisper), and server/analyze (llm).
- server/tests/common/artefacts.rs re-exports ONELINER_FILENAME
  (doctate-common), DOCUMENT_FILE + ANALYSIS_INPUT_FILE
  (doctate-server::analyze), and CLOSE_MARKER (doctate-server::paths) so
  test files reference the canonical name instead of inlining literals.

Also lifts client-desktop local duplication:
- paths.rs: project_path helper collapses 4 identical ProjectDirs chains
- main.rs: or_die helper replaces 4 eprintln!+exit(1) blocks
- app.rs: named RecordingContext struct replaces (Uuid, String) tuple
  at 3 sites around the ffmpeg-flush finalization path

Verification: 397 tests pass (baseline was 389; +8 for new unit tests on
BulkAction and join_url), 0 failed, 4 ignored (unchanged). clippy clean.
2026-04-22 12:09:46 +02:00
Brummel f438e972d4 refactor(tests): introduce shared tests/common/ support module
Lift duplicated test-harness code (User factories, TestConfig builder,
login/CSRF flow, form/json helpers, case-directory seeding, URL path
builders) into `server/tests/common/` so future API changes touch one
file instead of every integration test.

Migrated batches: csrf_attack, auth, login, magic_link, security_headers
(28 tests); analyze, case_page, delete_recording, close_watermark,
retention_sweep (67 tests). All 95 tests still green.

Net line delta across these 10 files: +1113 / -1896 (~780 lines removed),
plus ~500 lines of new shared infrastructure under tests/common/.
2026-04-22 10:42:51 +02:00
Brummel bf3e9ba6cc feat: server-side CSRF token validation
Introduces CsrfForm<T>: a FromRequest extractor that looks up the
session's csrf_token and constant-time-compares it to a csrf_token
field in the form body. Drop-in replacement for Form<T> on every
state-changing /web/ POST handler. Missing or expired session →
redirect to /web/login; malformed body → 400; token mismatch → 403.

WebSession carries csrf_token, minted alongside the session token at
login and magic-link consume. Not rotated per request — rotation
would break multi-tab use and buys little over SameSite=Strict
cookies.

Handlers: bulk, purge-closed, reset, close, reopen, analyze,
delete-recording, logout all now require CsrfForm<_>. Login stays
unprotected (SameSite=Strict alone is sufficient — forced-login CSRF
has no impact on this codebase). /api/... is header-auth, exempt.

Constant-time compare via subtle::ConstantTimeEq avoids timing
oracles on the token.

Template work is NOT in this commit — browser forms still post
without csrf_token, so the live web UI will 403 until Schritt 6
(templates render the hidden field).

Tests: all 12 red specs in csrf_attack_test.rs now green, #[ignore]
removed. Integration tests that POSTed to protected endpoints
switched to a new create_router_and_session_store entrypoint which
returns a handle to the store so tests can read csrf_token for the
active session.
2026-04-22 09:59:17 +02:00
Brummel 1182f4817e Enforce admin-only for bulk and purge actions
Move admin checks from individual bulk actions to the main handler for
`bulk.rs` and `case_actions.rs`. This consolidates the authorization
logic for these sensitive operations.

Additionally, refine the HTML template to conditionally render bulk
action UI elements and the purge form only when the user is an admin.
This ensures that non-admin users do not see or have access to these
administrative functions.

Update tests to reflect these changes, ensuring that non-admin users are
correctly rejected for these actions and that the UI is properly hidden.
2026-04-21 19:48:24 +02:00
Brummel 661ea6215e Add analysis preview to case list
Introduce a `preview_lines` setting in `users.toml` to control the
number of visible lines for the analysis preview in the case list. This
feature extracts the first paragraph of the `document.md` file, strips
markdown formatting, and displays it, capped by the configured
`preview_lines`. The actual line clamping is handled client-side via CSS
`line-clamp`.
2026-04-21 17:25:14 +02:00
Brummel 3218fc62bd feat: server-authoritative case window via users.toml
The /api/oneliners time window is now read per-user from users.toml
(window_hours, default 72h). Clients no longer carry a window:
client.toml oneliner_window_hours, SyncConfig.window_hours, the
?hours=N query param, and the render_case_list cutoff filter are
gone. ETag suffix keeps the effective hours so an admin edit to
users.toml invalidates client caches on the next request.
OnelinersResponse.window_hours stays in the wire format, but now
exists solely to anchor client reconciliation.
2026-04-21 16:48:01 +02:00
Brummel e5a62d8f10 feat: add per-user retention settings to users.toml
New [user.retention] TOML sub-block with auto_close_days and
auto_delete_days. Both default to 0, which disables the respective
sweep — a conservative default that preserves current behaviour for
existing deployments and lets admins test auto-close in isolation.

Only parsing + the test User literals are touched here; the actual
lazy sweep consuming these values lands in the next commit.
2026-04-21 10:48:24 +02:00
Brummel 396565a571 refactor: replace delete/undo flow with close/reopen endpoints
- POST /web/cases/:id/delete renamed to /close; emits CaseClosed.
- New POST /web/cases/:id/reopen removes the marker; emits CaseReopened.
- Bulk action "delete" renamed to "close"; the shared closed_at timestamp
  still groups the selection for future UI features.
- POST /web/cases/undo-delete removed along with summarize_latest_close_group,
  latest_close_timestamp and restore_close_group. Per-case reopen makes
  the group-undo banner obsolete.
- CaseEventKind::CaseDeleted -> CaseClosed, CaseRestored -> CaseReopened.
- Templates updated to Lucide trash-2 icon, German label "Fall schliessen".
- Tests migrated; delete_watermark_test.rs moved to close_watermark_test.rs.
2026-04-21 10:43:39 +02:00