Files
doctate/server/Cargo.toml
T
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

40 lines
1.1 KiB
TOML

[package]
name = "doctate-server"
version.workspace = true
edition.workspace = true
default-run = "doctate-server"
[dependencies]
doctate-common = { path = "../doctate-common" }
axum = { version = "0.8", features = ["multipart"] }
tokio.workspace = true
reqwest.workspace = true
tempfile = "3"
askama = "0.12"
tracing.workspace = true
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
tracing-appender = "0.2"
dotenvy = "0.15"
uuid.workspace = true
serde.workspace = true
serde_json.workspace = true
toml.workspace = true
tower-http = { version = "0.6", features = ["limit", "trace", "set-header"] }
tokio-stream = { version = "0.1", features = ["sync"] }
futures-util = "0.3"
rand = "0.8"
bcrypt = "0.15"
axum-extra = { version = "0.12", features = ["cookie", "form"] }
time.workspace = true
toml_edit = "0.25.11"
rpassword = "7.4.0"
pulldown-cmark = { version = "0.13", default-features = false, features = ["html"] }
strsim = "0.11"
spellbook = "0.4.0"
subtle = "2"
[dev-dependencies]
tower = { version = "0.5", features = ["util"] }
wiremock = "0.6"
filetime = "0.2"