bf3e9ba6cc
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.
40 lines
1.1 KiB
TOML
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"
|