use actix_web::{web, Responder, FromRequest, HttpRequest, HttpResponse, dev::Handler, http::Method};
use crate::core::auth::{validate_token};
pub struct Routes {
routes: Vec<Box<dyn Fn(&mut web::ServiceConfig) + Send + Sync>>,
}
impl Routes {
pub fn new() -> Self {
Self { routes: Vec::new() }
}
pub fn add_route_with_password<H, Args, R>(
self,
method: Method,
path: &'static str,
handler: H,
password: &'static str,
) -> Self
where
H: Handler<Args, Output = R> + Clone + Send + Sync + 'static,
Args: FromRequest + 'static,
R: Responder + 'static,
{
self.add_route_internal(method, path, handler, Some(password))
}
pub fn add_route<H, Args, R>(self, method: Method, path: &'static str, handler: H) -> Self
where
H: Handler<Args, Output = R> + Clone + Send + Sync + 'static,
Args: FromRequest + 'static,
R: Responder + 'static,
{
self.add_route_internal(method, path, handler, None)
}
pub fn add_route_with_auth<H, R>(mut self, method: Method, path: &'static str, handler: H) -> Self
where
H: Fn(HttpRequest, i32) -> R + Clone + Send + Sync + 'static,
R: futures_util::Future<Output = HttpResponse> + 'static,
{
let wrapped_handler = move |req: HttpRequest| {
let handler = handler.clone();
async move {
let token = match req
.headers()
.get("Authorization")
.and_then(|h| h.to_str().ok())
.and_then(|h| h.strip_prefix("Bearer "))
{
Some(token) => token,
None => return HttpResponse::Unauthorized().body("Missing or invalid token"),
};
let user_id = match validate_token(token) {
Ok(claims) => claims.sub,
Err(_) => return HttpResponse::Unauthorized().body("Invalid token"),
};
handler(req, user_id).await
}
};
let m = method.clone();
let route = {
let wrapped_handler = wrapped_handler.clone(); move |cfg: &mut web::ServiceConfig| {
cfg.service(
web::resource(path).route(web::method(m.clone()).to(wrapped_handler.clone()))
);
}
};
self.routes.push(Box::new(route));
self
}
fn add_route_internal<H, Args, R>(
mut self,
method: Method,
path: &'static str,
handler: H,
password: Option<&'static str>,
) -> Self
where
H: Handler<Args, Output = R> + Clone + Send + Sync + 'static,
Args: FromRequest + 'static,
R: Responder + 'static,
{
let handler = handler.clone(); let wrapped_handler = move |req: HttpRequest, args: Args| {
let handler = handler.clone(); async move {
if let Some(expected_password) = password {
if !check_password(&req, expected_password) {
return HttpResponse::Unauthorized().body("Invalid password").into();
}
}
handler.call(args).await.respond_to(&req).map_into_boxed_body()
}
};
let m = method.clone();
let route = move |cfg: &mut web::ServiceConfig| {
let wrapped_handler = wrapped_handler.clone(); cfg.service(
web::resource(path).route(web::method(m.clone()).to(wrapped_handler.clone()))
);
};
self.routes.push(Box::new(route));
self
}
pub fn configure(&self, cfg: &mut web::ServiceConfig) {
for route in &self.routes {
route(cfg);
}
}
}
fn check_password(req: &HttpRequest, expected_password: &str) -> bool {
let query_string = req.query_string();
for pair in query_string.split('&') {
let mut key_value = pair.splitn(2, '=');
if let (Some(key), Some(value)) = (key_value.next(), key_value.next()) {
if key == "password" && value == expected_password {
return true;
}
}
}
false
}