//! router aims to handle correctly the request corresponding to the target //! it implements all the logic to build an `HTTPResponse` use super::{HTTPMessage, HTTPRequest, HTTPResponse}; use crate::config::Config; use crate::stores::FileStore; use crate::stores::Store; use jwt_simple::prelude::*; use lazy_static::lazy_static; use std::collections::HashMap; use std::future::Future; use std::pin::Pin; type FuturePinned = Pin>>; type Handler = fn(HTTPRequest, Config) -> FuturePinned; fn handle_get(request: HTTPRequest, config: Config) -> FuturePinned { Box::pin(async move { let mut store = FileStore::new(config.filestore_path); match &request.body { Some(ref b) => { let is_auth = store.is_auth(&b.get_data()).await; if !is_auth { return HTTPResponse::as_403(); } let priv_key_content = { match std::fs::read_to_string(config.jwt_priv_key) { Ok(c) => c, Err(e) => { eprintln!("error while reading JWT priv key content err={}", e); "".to_string() } } }; let jwt_key = { match RS384KeyPair::from_pem(priv_key_content.as_str()) { Ok(k) => k, Err(e) => { let message: Option = HTTPMessage::error( format!("unable to load the private key, err={}", e).as_str(), ); return HTTPResponse::as_500(message); } } }; let mut claims = Claims::create(Duration::from_hours(config.jwt_exp_time)); claims.issuer = Some(config.jwt_issuer); match jwt_key.sign(claims) { Ok(token) => HTTPResponse::send_token(&token), Err(e) => { let message: Option = HTTPMessage::error( format!("unable to sign the token, err={}", e).as_str(), ); return HTTPResponse::as_500(message); } } } None => HTTPResponse::as_400(), } }) } /// validates the token by checking: /// * expiration time fn handle_validate(request: HTTPRequest, _config: Config) -> FuturePinned { Box::pin(async move { match &request.body { Some(ref _b) => { // TODO: impl the JWT validation HTTPResponse::send_token("header.payload.signature") } None => HTTPResponse::as_400(), } }) } lazy_static! { /// defines the map between the URL and its associated callback /// each authorized targets must implement a function returning `FuturePinned` // TODO: a macro should be implemented to mask the implementation details static ref HTTP_METHODS: HashMap<&'static str, Handler> = HashMap::from( [ ("/get/", handle_get as Handler), ("/validate/", handle_validate as Handler) ] ); } pub struct Router; impl Router { pub async fn route(&self, request_str: &str, config: Config) -> HTTPResponse { let request = HTTPRequest::from(request_str); let target = request.start_line.get_target(); match HTTP_METHODS.get(target.as_str()) { Some(f) => f(request, config).await, None => HTTPResponse::as_404(), } } } // this MUST be used like a Singleton pub const ROUTER: Router = Router {}; #[tokio::test] async fn test_route() { use super::HTTPStatusCode; let router: &Router = &ROUTER; let config: Config = Config::default(); let request_str = "POST /get/ HTTP/1.1\r\n\r\n"; let response: HTTPResponse = router.route(request_str, config).await; assert_eq!( HTTPStatusCode::Http400, response.status_line.get_status_code() ); }