-use crate::{
- settings::structs::{RateLimitConfig, Settings},
- utils::get_ip,
- IpAddr,
- LemmyError,
+use crate::{utils::get_ip, IpAddr};
+use actix_web::{
+ dev::{Service, ServiceRequest, ServiceResponse, Transform},
+ HttpResponse,
};
-use actix_web::dev::{Service, ServiceRequest, ServiceResponse, Transform};
use futures::future::{ok, Ready};
use rate_limiter::{RateLimitType, RateLimiter};
+use serde::{Deserialize, Serialize};
use std::{
future::Future,
pin::Pin,
- sync::Arc,
+ rc::Rc,
+ sync::{Arc, Mutex},
task::{Context, Poll},
};
-use tokio::sync::Mutex;
+use typed_builder::TypedBuilder;
pub mod rate_limiter;
+#[derive(Debug, Deserialize, Serialize, Clone, TypedBuilder)]
+pub struct RateLimitConfig {
+ #[builder(default = 180)]
+ /// Maximum number of messages created in interval
+ pub message: i32,
+ #[builder(default = 60)]
+ /// Interval length for message limit, in seconds
+ pub message_per_second: i32,
+ #[builder(default = 6)]
+ /// Maximum number of posts created in interval
+ pub post: i32,
+ #[builder(default = 300)]
+ /// Interval length for post limit, in seconds
+ pub post_per_second: i32,
+ #[builder(default = 3)]
+ /// Maximum number of registrations in interval
+ pub register: i32,
+ #[builder(default = 3600)]
+ /// Interval length for registration limit, in seconds
+ pub register_per_second: i32,
+ #[builder(default = 6)]
+ /// Maximum number of image uploads in interval
+ pub image: i32,
+ #[builder(default = 3600)]
+ /// Interval length for image uploads, in seconds
+ pub image_per_second: i32,
+ #[builder(default = 6)]
+ /// Maximum number of comments created in interval
+ pub comment: i32,
+ #[builder(default = 600)]
+ /// Interval length for comment limit, in seconds
+ pub comment_per_second: i32,
+ #[builder(default = 60)]
+ /// Maximum number of searches created in interval
+ pub search: i32,
+ #[builder(default = 600)]
+ /// Interval length for search limit, in seconds
+ pub search_per_second: i32,
+}
+
#[derive(Debug, Clone)]
pub struct RateLimit {
// it might be reasonable to use a std::sync::Mutex here, since we don't need to lock this
// across await points
pub rate_limiter: Arc<Mutex<RateLimiter>>,
+ pub rate_limit_config: RateLimitConfig,
}
#[derive(Debug, Clone)]
pub struct RateLimited {
rate_limiter: Arc<Mutex<RateLimiter>>,
+ rate_limit_config: RateLimitConfig,
type_: RateLimitType,
}
pub struct RateLimitedMiddleware<S> {
rate_limited: RateLimited,
- service: S,
+ service: Rc<S>,
}
impl RateLimit {
self.kind(RateLimitType::Image)
}
+ pub fn comment(&self) -> RateLimited {
+ self.kind(RateLimitType::Comment)
+ }
+
+ pub fn search(&self) -> RateLimited {
+ self.kind(RateLimitType::Search)
+ }
+
fn kind(&self, type_: RateLimitType) -> RateLimited {
RateLimited {
rate_limiter: self.rate_limiter.clone(),
+ rate_limit_config: self.rate_limit_config.clone(),
type_,
}
}
}
impl RateLimited {
- pub async fn wrap<T, E>(
- self,
- ip_addr: IpAddr,
- fut: impl Future<Output = Result<T, E>>,
- ) -> Result<T, E>
- where
- E: From<LemmyError>,
- {
+ /// Returns true if the request passed the rate limit, false if it failed and should be rejected.
+ pub fn check(self, ip_addr: IpAddr) -> bool {
// Does not need to be blocking because the RwLock in settings never held across await points,
// and the operation here locks only long enough to clone
- let rate_limit: RateLimitConfig = Settings::get().rate_limit();
-
- // before
- {
- let mut limiter = self.rate_limiter.lock().await;
-
- match self.type_ {
- RateLimitType::Message => {
- limiter.check_rate_limit_full(
- self.type_,
- &ip_addr,
- rate_limit.message,
- rate_limit.message_per_second,
- false,
- )?;
-
- drop(limiter);
- return fut.await;
- }
- RateLimitType::Post => {
- limiter.check_rate_limit_full(
- self.type_,
- &ip_addr,
- rate_limit.post,
- rate_limit.post_per_second,
- true,
- )?;
- }
- RateLimitType::Register => {
- limiter.check_rate_limit_full(
- self.type_,
- &ip_addr,
- rate_limit.register,
- rate_limit.register_per_second,
- true,
- )?;
- }
- RateLimitType::Image => {
- limiter.check_rate_limit_full(
- self.type_,
- &ip_addr,
- rate_limit.image,
- rate_limit.image_per_second,
- false,
- )?;
- }
- };
- }
-
- let res = fut.await;
-
- // after
- {
- let mut limiter = self.rate_limiter.lock().await;
- if res.is_ok() {
- match self.type_ {
- RateLimitType::Post => {
- limiter.check_rate_limit_full(
- self.type_,
- &ip_addr,
- rate_limit.post,
- rate_limit.post_per_second,
- false,
- )?;
- }
- RateLimitType::Register => {
- limiter.check_rate_limit_full(
- self.type_,
- &ip_addr,
- rate_limit.register,
- rate_limit.register_per_second,
- false,
- )?;
- }
- _ => (),
- };
- }
- }
-
- res
+ let rate_limit = self.rate_limit_config;
+
+ let (kind, interval) = match self.type_ {
+ RateLimitType::Message => (rate_limit.message, rate_limit.message_per_second),
+ RateLimitType::Post => (rate_limit.post, rate_limit.post_per_second),
+ RateLimitType::Register => (rate_limit.register, rate_limit.register_per_second),
+ RateLimitType::Image => (rate_limit.image, rate_limit.image_per_second),
+ RateLimitType::Comment => (rate_limit.comment, rate_limit.comment_per_second),
+ RateLimitType::Search => (rate_limit.search, rate_limit.search_per_second),
+ };
+ let mut limiter = self.rate_limiter.lock().expect("mutex poison error");
+
+ limiter.check_rate_limit_full(self.type_, &ip_addr, kind, interval)
}
}
impl<S> Transform<S, ServiceRequest> for RateLimited
where
- S: Service<ServiceRequest, Response = ServiceResponse, Error = actix_web::Error>,
+ S: Service<ServiceRequest, Response = ServiceResponse, Error = actix_web::Error> + 'static,
S::Future: 'static,
{
type Response = S::Response;
fn new_transform(&self, service: S) -> Self::Future {
ok(RateLimitedMiddleware {
rate_limited: self.clone(),
- service,
+ service: Rc::new(service),
})
}
}
impl<S> Service<ServiceRequest> for RateLimitedMiddleware<S>
where
- S: Service<ServiceRequest, Response = ServiceResponse, Error = actix_web::Error>,
+ S: Service<ServiceRequest, Response = ServiceResponse, Error = actix_web::Error> + 'static,
S::Future: 'static,
{
type Response = S::Response;
fn call(&self, req: ServiceRequest) -> Self::Future {
let ip_addr = get_ip(&req.connection_info());
- let fut = self
- .rate_limited
- .clone()
- .wrap(ip_addr, self.service.call(req));
-
- Box::pin(async move { fut.await.map_err(actix_web::Error::from) })
+ let rate_limited = self.rate_limited.clone();
+ let service = self.service.clone();
+
+ Box::pin(async move {
+ if rate_limited.check(ip_addr) {
+ service.call(req).await
+ } else {
+ let (http_req, _) = req.into_parts();
+ // if rate limit was hit, respond with http 400
+ Ok(ServiceResponse::new(
+ http_req,
+ HttpResponse::BadRequest().finish(),
+ ))
+ }
+ })
}
}