use std::time::Duration; use crate::cache::Cache; use crate::errors::{AuthError, AuthResult}; #[tracing::instrument(skip_all, fields(request_id = %envelope::context::resolve_request_id(_request_id)))] pub async fn rate_limit_once( cache: &Cache, key: &str, window_secs: u64, _request_id: Option<&str>, ) -> AuthResult<()> { let window = window_secs.max(1); let is_new = cache.set_nx_ex(key, 1, Duration::from_secs(window)).await?; if !is_new { return Err(AuthError::RateLimited { retry_after_secs: window, }); } Ok(()) } #[tracing::instrument(skip_all, fields(request_id = %envelope::context::resolve_request_id(_request_id)))] pub async fn attempts_track( cache: &Cache, key: &str, max: u64, window_secs: u64, _request_id: Option<&str>, ) -> AuthResult { let count = cache .scripts() .incr_ex(key, Duration::from_secs(window_secs.max(1))) .await?; Ok(count <= max as i64) } #[tracing::instrument(skip_all, fields(request_id = %envelope::context::resolve_request_id(_request_id)))] pub async fn attempts_load(cache: &Cache, key: &str, _request_id: Option<&str>) -> AuthResult { let count: Option = cache.get(key).await?; Ok(count.unwrap_or_default().max(0) as u64) } #[tracing::instrument(skip_all, fields(request_id = %envelope::context::resolve_request_id(_request_id)))] pub async fn attempts_reset(cache: &Cache, key: &str, _request_id: Option<&str>) -> AuthResult<()> { cache.delete(key).await?; Ok(()) }