fix: restore Instant for rate limiters, keep SystemTime for sessions

Rate limiters correctly use monotonic Instant. Session TTL uses
SystemTime for wall-clock accuracy across sleep/hibernate.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Dorian 2026-03-15 04:36:23 +00:00
parent 0b3bf5b635
commit 979e1c7411

View File

@ -2,7 +2,7 @@ use sha2::{Digest, Sha256};
use std::collections::HashMap; use std::collections::HashMap;
use std::net::IpAddr; use std::net::IpAddr;
use std::sync::Arc; use std::sync::Arc;
use std::time::SystemTime; use std::time::{Instant, SystemTime};
use tokio::sync::RwLock; use tokio::sync::RwLock;
use zeroize::Zeroize; use zeroize::Zeroize;
@ -262,7 +262,7 @@ impl LoginRateLimiter {
pub async fn check(&self, ip: IpAddr) -> bool { pub async fn check(&self, ip: IpAddr) -> bool {
let mut attempts = self.attempts.write().await; let mut attempts = self.attempts.write().await;
let now = SystemTime::now(); let now = Instant::now();
let entry = attempts.entry(ip).or_default(); let entry = attempts.entry(ip).or_default();
entry.retain(|t| now.duration_since(*t).as_secs() < WINDOW_SECS); entry.retain(|t| now.duration_since(*t).as_secs() < WINDOW_SECS);
entry.len() < MAX_ATTEMPTS entry.len() < MAX_ATTEMPTS
@ -271,7 +271,7 @@ impl LoginRateLimiter {
pub async fn record_failure(&self, ip: IpAddr) { pub async fn record_failure(&self, ip: IpAddr) {
let mut attempts = self.attempts.write().await; let mut attempts = self.attempts.write().await;
let entry = attempts.entry(ip).or_default(); let entry = attempts.entry(ip).or_default();
entry.push(SystemTime::now()); entry.push(Instant::now());
} }
} }
@ -280,7 +280,7 @@ impl LoginRateLimiter {
#[derive(Clone)] #[derive(Clone)]
pub struct EndpointRateLimiter { pub struct EndpointRateLimiter {
/// Map of (method, ip) -> list of request timestamps /// Map of (method, ip) -> list of request timestamps
requests: Arc<RwLock<HashMap<(String, IpAddr), Vec<Instant>>>>, requests: Arc<RwLock<HashMap<(String, IpAddr), Vec<Instant>>>>, // Instant for monotonic rate limiting
/// Per-method configuration: (max_requests, window_secs) /// Per-method configuration: (max_requests, window_secs)
limits: Arc<HashMap<String, (usize, u64)>>, limits: Arc<HashMap<String, (usize, u64)>>,
} }
@ -338,7 +338,7 @@ impl EndpointRateLimiter {
let key = (method.to_string(), ip); let key = (method.to_string(), ip);
let mut requests = self.requests.write().await; let mut requests = self.requests.write().await;
let now = SystemTime::now(); let now = Instant::now();
let entry = requests.entry(key).or_default(); let entry = requests.entry(key).or_default();
entry.retain(|t| now.duration_since(*t).as_secs() < window); entry.retain(|t| now.duration_since(*t).as_secs() < window);
entry.len() < max_req entry.len() < max_req
@ -352,13 +352,13 @@ impl EndpointRateLimiter {
let key = (method.to_string(), ip); let key = (method.to_string(), ip);
let mut requests = self.requests.write().await; let mut requests = self.requests.write().await;
let entry = requests.entry(key).or_default(); let entry = requests.entry(key).or_default();
entry.push(SystemTime::now()); entry.push(Instant::now());
} }
/// Periodic cleanup of expired entries. /// Periodic cleanup of expired entries.
pub async fn cleanup(&self) { pub async fn cleanup(&self) {
let mut requests = self.requests.write().await; let mut requests = self.requests.write().await;
let now = SystemTime::now(); let now = Instant::now();
requests.retain(|(method, _), timestamps| { requests.retain(|(method, _), timestamps| {
let window = self let window = self
.limits .limits