gitdataai/libs/transport/security.rs
ZhenYi 14f6e1e500 feat(core): initialize project with access control and AI integration
- 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
2026-05-03 06:04:31 +08:00

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)
}
}