mirror of
				https://gitlab.com/comunic/comunicapiv3
				synced 2025-11-04 01:24:04 +00:00 
			
		
		
		
	Make limit system lives
This commit is contained in:
		@@ -34,6 +34,21 @@ pub enum LimitPolicy {
 | 
				
			|||||||
    ANY(u64),
 | 
					    ANY(u64),
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl LimitPolicy {
 | 
				
			||||||
 | 
					    pub fn is_none(&self) -> bool {
 | 
				
			||||||
 | 
					        matches!(self, LimitPolicy::NONE)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub fn get_count(&self) -> u64 {
 | 
				
			||||||
 | 
					        match self {
 | 
				
			||||||
 | 
					            LimitPolicy::NONE => 0,
 | 
				
			||||||
 | 
					            LimitPolicy::SUCCESS(n) => n.clone(),
 | 
				
			||||||
 | 
					            LimitPolicy::FAILURE(n) => n.clone(),
 | 
				
			||||||
 | 
					            LimitPolicy::ANY(n) => n.clone(),
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/// Define types
 | 
					/// Define types
 | 
				
			||||||
pub type RequestResult = Result<(), Box<dyn Error>>;
 | 
					pub type RequestResult = Result<(), Box<dyn Error>>;
 | 
				
			||||||
pub type RequestProcess = Box<dyn Fn(&mut HttpRequestHandler) -> RequestResult>;
 | 
					pub type RequestProcess = Box<dyn Fn(&mut HttpRequestHandler) -> RequestResult>;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -196,6 +196,9 @@ impl FromRequest for CustomRequest {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
/// Process a "simple request" aka not a WebSocket request
 | 
					/// Process a "simple request" aka not a WebSocket request
 | 
				
			||||||
fn process_simple_route(route: &Route, req: &mut HttpRequestHandler) -> RequestResult {
 | 
					fn process_simple_route(route: &Route, req: &mut HttpRequestHandler) -> RequestResult {
 | 
				
			||||||
 | 
					    if requests_limit_helper::trigger_before(req, route).is_err() {
 | 
				
			||||||
 | 
					        req.too_many_requests("Too many request. Please try again later.")?;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // Validate client token
 | 
					    // Validate client token
 | 
				
			||||||
    req.check_client_token()?;
 | 
					    req.check_client_token()?;
 | 
				
			||||||
@@ -205,7 +208,11 @@ fn process_simple_route(route: &Route, req: &mut HttpRequestHandler) -> RequestR
 | 
				
			|||||||
        req.check_user_token()?;
 | 
					        req.check_user_token()?;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    (route.func)(req)
 | 
					    let res: RequestResult = (route.func)(req);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    requests_limit_helper::trigger_after(res.is_ok(), req, route)?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    res
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/// Process an incoming request
 | 
					/// Process an incoming request
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -146,6 +146,13 @@ impl HttpRequestHandler {
 | 
				
			|||||||
        Err(Box::new(ExecError::new(&message)))
 | 
					        Err(Box::new(ExecError::new(&message)))
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /// Too many requests (429)
 | 
				
			||||||
 | 
					    pub fn too_many_requests(&mut self, message: &str) -> RequestResult {
 | 
				
			||||||
 | 
					        self.response = Some(HttpResponse::TooManyRequests().json(
 | 
				
			||||||
 | 
					            HttpError::new(429, message)));
 | 
				
			||||||
 | 
					        Err(Box::new(ExecError::new(message)))
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /// If result is not OK, return a bad request
 | 
					    /// If result is not OK, return a bad request
 | 
				
			||||||
    pub fn ok_or_bad_request<E>(&mut self, res: ResultBoxError<E>, msg: &str) -> ResultBoxError<E> {
 | 
					    pub fn ok_or_bad_request<E>(&mut self, res: ResultBoxError<E>, msg: &str) -> ResultBoxError<E> {
 | 
				
			||||||
        match res {
 | 
					        match res {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -2,26 +2,32 @@
 | 
				
			|||||||
//!
 | 
					//!
 | 
				
			||||||
//! Handle the limitation of requests, depending on threshold criterias
 | 
					//! Handle the limitation of requests, depending on threshold criterias
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
use std::sync::{Arc, Mutex};
 | 
					use std::sync::{Arc, Mutex};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
use crate::constants::LIMIT_COUNTER_LIFETIME;
 | 
					use crate::constants::LIMIT_COUNTER_LIFETIME;
 | 
				
			||||||
use crate::data::error::ResultBoxError;
 | 
					use crate::controllers::routes::{LimitPolicy, Route};
 | 
				
			||||||
 | 
					use crate::data::error::{ExecError, ResultBoxError};
 | 
				
			||||||
 | 
					use crate::data::http_request_handler::HttpRequestHandler;
 | 
				
			||||||
use crate::utils::date_utils;
 | 
					use crate::utils::date_utils;
 | 
				
			||||||
 | 
					use crate::utils::date_utils::time;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/// Information about a IP address limitation
 | 
					/// Information about a IP address limitation
 | 
				
			||||||
struct IpInfo {
 | 
					struct IpInfo {
 | 
				
			||||||
 | 
					    ip: String,
 | 
				
			||||||
    time_start: u64,
 | 
					    time_start: u64,
 | 
				
			||||||
 | 
					    uri: String,
 | 
				
			||||||
    count: u64,
 | 
					    count: u64,
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/// Limits cache
 | 
					/// Limits cache
 | 
				
			||||||
type Cache = Arc<dashmap::DashMap<String, IpInfo>>;
 | 
					type Cache = Vec<IpInfo>;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
static mut LIMITS_CACHE: Option<Arc<Mutex<Cache>>> = None;
 | 
					static mut LIMITS_CACHE: Option<Arc<Mutex<Cache>>> = None;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/// Initialize limit cache storage
 | 
					/// Initialize limit cache storage
 | 
				
			||||||
pub fn init() {
 | 
					pub fn init() {
 | 
				
			||||||
    let limits_cache = Arc::new(dashmap::DashMap::new());
 | 
					    let limits_cache = Vec::new();
 | 
				
			||||||
    let limits_cache = Some(Arc::new(Mutex::new(limits_cache)));
 | 
					    let limits_cache = Some(Arc::new(Mutex::new(limits_cache)));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    unsafe {
 | 
					    unsafe {
 | 
				
			||||||
@@ -30,12 +36,11 @@ pub fn init() {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/// Get access to the cache. This resource must absolutely be released as quickly as possible
 | 
					/// Get access to the cache. This resource must absolutely be released as quickly as possible
 | 
				
			||||||
fn get_cache() -> ResultBoxError<Cache> {
 | 
					fn get_cache() -> ResultBoxError<Arc<Mutex<Cache>>> {
 | 
				
			||||||
    let cache: Cache;
 | 
					    let cache;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    unsafe {
 | 
					    unsafe {
 | 
				
			||||||
        let guard = LIMITS_CACHE.as_ref().unwrap().lock();
 | 
					        cache = LIMITS_CACHE.as_ref().unwrap().clone();
 | 
				
			||||||
        cache = guard.unwrap().clone();
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    Ok(cache)
 | 
					    Ok(cache)
 | 
				
			||||||
@@ -45,16 +50,82 @@ fn get_cache() -> ResultBoxError<Cache> {
 | 
				
			|||||||
pub fn clean_cache() -> ResultBoxError {
 | 
					pub fn clean_cache() -> ResultBoxError {
 | 
				
			||||||
    let time = date_utils::time();
 | 
					    let time = date_utils::time();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    let cache = get_cache()?;
 | 
					    let mut i = 0;
 | 
				
			||||||
    let obsolete_ips: Vec<String> = cache
 | 
					 | 
				
			||||||
        .iter()
 | 
					 | 
				
			||||||
        .filter(|k| k.time_start + LIMIT_COUNTER_LIFETIME < time)
 | 
					 | 
				
			||||||
        .map(|k| k.key().to_string())
 | 
					 | 
				
			||||||
        .collect();
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    for obsolete_ip in obsolete_ips {
 | 
					    let cache = get_cache()?;
 | 
				
			||||||
        cache.remove(&obsolete_ip);
 | 
					    let mut cache = cache.lock().unwrap();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    while i < cache.len() {
 | 
				
			||||||
 | 
					        if cache[i].time_start + LIMIT_COUNTER_LIFETIME < time {
 | 
				
			||||||
 | 
					            cache.remove(i);
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            i = i + 1;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Ok(())
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// Trigger limit helper at the beginning of requests
 | 
				
			||||||
 | 
					pub fn trigger_before(req: &HttpRequestHandler, route: &Route) -> ResultBoxError {
 | 
				
			||||||
 | 
					    if route.limit_policy.is_none() {
 | 
				
			||||||
 | 
					        return Ok(());
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    let max_count = route.limit_policy.get_count();
 | 
				
			||||||
 | 
					    let ip = req.remote_ip();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    let cache = get_cache()?;
 | 
				
			||||||
 | 
					    let found = cache.lock().unwrap()
 | 
				
			||||||
 | 
					        .iter()
 | 
				
			||||||
 | 
					        .find(
 | 
				
			||||||
 | 
					            |k| k.uri.eq(route.uri)
 | 
				
			||||||
 | 
					                && k.count >= max_count
 | 
				
			||||||
 | 
					                && k.ip.eq(&ip))
 | 
				
			||||||
 | 
					        .is_some();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if found {
 | 
				
			||||||
 | 
					        return Err(ExecError::boxed_new("Limit exceeded!"));
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Ok(())
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// Trigger limit at the end of the request
 | 
				
			||||||
 | 
					pub fn trigger_after(is_success: bool, req: &HttpRequestHandler, route: &Route) -> ResultBoxError {
 | 
				
			||||||
 | 
					    let need_trigger = match (&route.limit_policy, is_success)
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        (LimitPolicy::NONE, _) => false,
 | 
				
			||||||
 | 
					        (LimitPolicy::ANY(_), _) => true,
 | 
				
			||||||
 | 
					        (LimitPolicy::SUCCESS(_), res) => res,
 | 
				
			||||||
 | 
					        (LimitPolicy::FAILURE(_), res) => !res,
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if !need_trigger {
 | 
				
			||||||
 | 
					        return Ok(());
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    let ip = req.remote_ip();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    let cache = get_cache()?;
 | 
				
			||||||
 | 
					    let mut cache = cache.lock().unwrap();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // We search for existing entry
 | 
				
			||||||
 | 
					    for i in 0..cache.len() {
 | 
				
			||||||
 | 
					        if cache[i].ip.eq(&ip) && cache[i].uri.eq(route.uri) {
 | 
				
			||||||
 | 
					            cache[i].count += 1;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            return Ok(());
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Otherwise we must add the entry to the table
 | 
				
			||||||
 | 
					    cache.push(IpInfo {
 | 
				
			||||||
 | 
					        ip,
 | 
				
			||||||
 | 
					        time_start: time(),
 | 
				
			||||||
 | 
					        uri: route.uri.to_string(),
 | 
				
			||||||
 | 
					        count: 1,
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    Ok(())
 | 
					    Ok(())
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
		Reference in New Issue
	
	Block a user