fix(audit-2026-06-11/H-B1): login rate limit + Argon2 concurrency cap
The login handler had no admission control before `spawn_blocking(verify_password)`. On Pi-class hardware Argon2id is ~50- 100 ms per attempt, so a few dozen concurrent anonymous POSTs to /auth/login park every blocking worker, wedging the entire admin API and any other path that uses `spawn_blocking`. Adds two cheap, in-process guards modeled on EmailRateLimiter: * `LoginRateLimiter` — two sliding-window token buckets, one per `(remote_ip, username)` (burst 5/60s) and one per `username` (burst 10/15min). Per-(ip, user) defeats a single attacker pounding one account; per-user defeats credential-stuffing distributed across many IPs. Username is case-folded so case variants share a bucket. Maps GC lazily when they cross a soft size cap. * Per-process `tokio::sync::Semaphore` — caps concurrent Argon2 verifies during login. Default 2 permits (Pi-class), overridable via `PICLOUD_LOGIN_ARGON2_PARALLELISM`. Acquired AFTER the bucket check so attackers can't queue. Limit check fires before the DB credentials lookup, so even an unknown username doesn't cost a query. Denied attempts return 429 + Retry-After. Real client IP comes from the first X-Forwarded-For entry (Caddy is the trusted single hop); falls back to "unknown" so the per-user bucket still gates. 4 unit tests cover bucket burst exhaustion, per-user crossing IPs, case folding, and per-user independence. Audit ref: security_audit/08_dos_resource.md (H-2 / H-B1). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -115,6 +115,13 @@ pub struct AuthState {
|
||||
/// at startup; cloned cheaply into every router state. `None` for
|
||||
/// tests / harnesses that don't wire it.
|
||||
pub principal_cache: Arc<PrincipalCache>,
|
||||
/// Audit 2026-06-11 H-B1 — per-`(remote_ip, username)` + per-`username`
|
||||
/// burst limiter for `/auth/login`. Cheap to clone (Arc).
|
||||
pub login_rate_limiter: Arc<crate::login_rate_limit::LoginRateLimiter>,
|
||||
/// Audit 2026-06-11 H-B1 — caps concurrent Argon2 verifies during
|
||||
/// login so a credential-stuffing flurry can't park every blocking
|
||||
/// worker. Sized via `PICLOUD_LOGIN_ARGON2_PARALLELISM` (default 2).
|
||||
pub argon2_login_semaphore: Arc<tokio::sync::Semaphore>,
|
||||
}
|
||||
|
||||
/// Legacy request-extension alias retained so the (only remaining)
|
||||
|
||||
Reference in New Issue
Block a user