Cut recurring Argon2 cost and remove per-lock WebDAV timers
Two hot-path fixes for DAV sync clients, which poll continuously: 1. App-password Basic Auth cache TTL 30s → 300s. Every cache miss costs a full Argon2id verification (~50-100ms CPU) plus two DB queries; at 30s a continuously-syncing client re-paid that every 30s. 5 min cuts it ~10x. Safe because revoke() already invalidates the user's cached entries immediately; expiry/deactivation are only re-checked on a miss, so they now have a <=5 min grace window (comparable to a JWT access-token lifetime) — documented on the constant. 2. WebDAV lock store: replace the tokio::spawn + sleep scheduled per acquire/refresh with Moka's per-entry Expiry policy. Office clients refresh locks constantly, leaving thousands of orphaned sleeping timers pinned in the runtime that were never cancelled. by_path now carries the exact per-lock TTL via LockExpiry (no background tasks); by_token keeps a 24h backstop and resolves through by_path, so a lingering reverse-index entry can never resurrect an expired lock. Adds unit tests for the expiry policy and acquire/refresh/release (the module had none). https://claude.ai/code/session_0193Hff42gaA962wThxMGSd1
This commit is contained in:
@@ -30,9 +30,25 @@ const NC_APP_PASSWORD_GROUP_LEN: usize = 5;
|
||||
const NC_PREFIX_LEN: usize = 8;
|
||||
|
||||
/// TTL for cached Basic Auth verification results.
|
||||
/// Balances performance (avoids repeated Argon2id + DB queries) with security
|
||||
/// (limits the window during which a revoked app password remains usable).
|
||||
const BASIC_AUTH_CACHE_TTL_SECS: u64 = 30;
|
||||
///
|
||||
/// DAV sync clients (Nautilus, Windows Explorer, Apple Calendar, …) poll
|
||||
/// continuously, and every cache miss costs a full Argon2id verification
|
||||
/// (~50–100 ms of CPU) plus two DB round-trips. A 30 s TTL re-paid that
|
||||
/// cost every 30 s per client; 5 min cuts it ~10× under steady sync load.
|
||||
///
|
||||
/// Security envelope of this window:
|
||||
/// - **Revocation is immediate**: `revoke()` calls `invalidate_entries_if`
|
||||
/// on this cache for the user, so a revoked password never survives in
|
||||
/// cache regardless of TTL.
|
||||
/// - **Expiry / deactivation are bounded by the TTL**: `expires_at` and
|
||||
/// `user.is_active()` are only re-checked on a cache *miss* (the DB
|
||||
/// query filters them), so an app password that expires — or a user
|
||||
/// deactivated via `set_user_active` — may keep authenticating from
|
||||
/// cache for at most this long. 5 min is comparable to a typical JWT
|
||||
/// access-token lifetime, so the grace window is consistent across
|
||||
/// auth surfaces. Lengthen with care; shorten if a tighter bound on
|
||||
/// post-deactivation access is required.
|
||||
const BASIC_AUTH_CACHE_TTL_SECS: u64 = 300;
|
||||
|
||||
/// Maximum number of cached Basic Auth verifications.
|
||||
/// Each entry is ~160 bytes (32-byte key + 4 small strings), so 10 000
|
||||
@@ -61,9 +77,9 @@ pub struct AppPasswordService {
|
||||
///
|
||||
/// **Value**: the authenticated identity (user_id, username, email, role).
|
||||
///
|
||||
/// **Eviction**: TTL-based (30 s) + capacity-based (10 000 entries).
|
||||
/// Failed verifications are *never* cached, so brute-force attackers
|
||||
/// always pay the full Argon2id cost.
|
||||
/// **Eviction**: TTL-based (see `BASIC_AUTH_CACHE_TTL_SECS`) +
|
||||
/// capacity-based (10 000 entries). Failed verifications are *never*
|
||||
/// cached, so brute-force attackers always pay the full Argon2id cost.
|
||||
auth_cache: Cache<[u8; 32], CachedBasicAuthResult>,
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user