- Add gitignore and prettier configuration files for project scaffolding - Implement room access control service with project member verification - Create user access key management with CRUD operations and activity logging - Add accordion UI component for frontend expandable sections - Implement room AI configuration with list, upsert, and delete operations - Add AI event types for agent join/leave/status change tracking - Create streaming AI processing services for mode and react patterns - Build room AI service with model detection and idempotency handling - Integrate chat service orchestration for AI message processing - Add typing indicators and stream cancellation for AI interactions - Implement mention parsing and context extraction for AI agents
114 lines
3.0 KiB
Rust
114 lines
3.0 KiB
Rust
use redis::AsyncCommands;
|
|
use std::time::Duration;
|
|
use uuid::Uuid;
|
|
|
|
#[derive(Clone)]
|
|
pub struct RateLimiter {
|
|
cache: db::cache::AppCache,
|
|
max_requests: u32,
|
|
window: Duration,
|
|
}
|
|
|
|
impl RateLimiter {
|
|
pub fn new(cache: db::cache::AppCache) -> Self {
|
|
Self {
|
|
cache,
|
|
max_requests: 100,
|
|
window: Duration::from_secs(60),
|
|
}
|
|
}
|
|
|
|
pub async fn check_rate_limit(
|
|
&self,
|
|
user_id: Uuid,
|
|
action: &str,
|
|
) -> Result<bool, crate::error::AppTransportError> {
|
|
let key = format!("ratelimit:{}:{}", user_id, action);
|
|
|
|
let mut conn = self.cache.conn().await
|
|
.map_err(|_| crate::error::AppTransportError::Internal)?;
|
|
|
|
let count: Option<u32> = conn.get(&key).await
|
|
.map_err(|_| crate::error::AppTransportError::Internal)?;
|
|
|
|
let current = count.unwrap_or(0);
|
|
|
|
if current >= self.max_requests {
|
|
return Ok(false);
|
|
}
|
|
|
|
let new_count = current + 1;
|
|
let _: () = conn.set_ex(&key, new_count, self.window.as_secs())
|
|
.await
|
|
.map_err(|_| crate::error::AppTransportError::Internal)?;
|
|
|
|
Ok(true)
|
|
}
|
|
|
|
pub async fn get_remaining(
|
|
&self,
|
|
user_id: Uuid,
|
|
action: &str,
|
|
) -> Result<u32, crate::error::AppTransportError> {
|
|
let key = format!("ratelimit:{}:{}", user_id, action);
|
|
|
|
let mut conn = self.cache.conn().await
|
|
.map_err(|_| crate::error::AppTransportError::Internal)?;
|
|
|
|
let count: Option<u32> = conn.get(&key).await
|
|
.map_err(|_| crate::error::AppTransportError::Internal)?;
|
|
|
|
let current = count.unwrap_or(0);
|
|
Ok(self.max_requests.saturating_sub(current))
|
|
}
|
|
}
|
|
|
|
#[derive(Clone)]
|
|
pub struct CsrfProtection {
|
|
cache: db::cache::AppCache,
|
|
}
|
|
|
|
impl CsrfProtection {
|
|
pub fn new(cache: db::cache::AppCache) -> Self {
|
|
Self { cache }
|
|
}
|
|
|
|
pub async fn generate_token(
|
|
&self,
|
|
user_id: Uuid,
|
|
) -> Result<String, crate::error::AppTransportError> {
|
|
let token = Uuid::new_v4().to_string();
|
|
let key = format!("csrf:{}:{}", user_id, token);
|
|
|
|
let mut conn = self.cache.conn().await
|
|
.map_err(|_| crate::error::AppTransportError::Internal)?;
|
|
|
|
let _: () = conn.set_ex(&key, "1", 3600)
|
|
.await
|
|
.map_err(|_| crate::error::AppTransportError::Internal)?;
|
|
|
|
Ok(token)
|
|
}
|
|
|
|
pub async fn validate_token(
|
|
&self,
|
|
user_id: Uuid,
|
|
token: &str,
|
|
) -> Result<bool, crate::error::AppTransportError> {
|
|
let key = format!("csrf:{}:{}", user_id, token);
|
|
|
|
let mut conn = self.cache.conn().await
|
|
.map_err(|_| crate::error::AppTransportError::Internal)?;
|
|
|
|
let exists: bool = conn.exists(&key).await
|
|
.map_err(|_| crate::error::AppTransportError::Internal)?;
|
|
|
|
if exists {
|
|
let _: () = conn.del(&key).await
|
|
.map_err(|_| crate::error::AppTransportError::Internal)?;
|
|
}
|
|
|
|
Ok(exists)
|
|
}
|
|
}
|