From 616631028359178ef62af310bcf79fa0371dc14f Mon Sep 17 00:00:00 2001 From: landrigun Date: Thu, 13 Oct 2022 16:06:27 +0000 Subject: [PATCH] refactor GET handler + impl JWTSigner --- src/config/config.rs | 2 - src/http/message.rs | 1 - src/http/request.rs | 2 - src/http/response.rs | 2 +- src/http/router.rs | 33 ++++---------- src/jwt/jwt.rs | 83 +++++++++++++++++++++++++++++++++++ src/jwt/mod.rs | 3 ++ src/main.rs | 1 + tests/bash/curling.bash | 2 +- tests/python/test_requests.py | 4 +- 10 files changed, 100 insertions(+), 33 deletions(-) create mode 100644 src/jwt/jwt.rs create mode 100644 src/jwt/mod.rs diff --git a/src/config/config.rs b/src/config/config.rs index 31d0bd8..1f77698 100644 --- a/src/config/config.rs +++ b/src/config/config.rs @@ -68,13 +68,11 @@ impl Config { return false; } - // TODO: check if the file exists and rights are ok if self.jwt_pub_key == "" { eprintln!("invalid config parameter: JWT public key file path is empty"); return false; } - // TODO: check if the file exists and rights are ok if self.jwt_priv_key == "" { eprintln!("invalid config parameter: JWT private key file path is empty"); return false; diff --git a/src/http/message.rs b/src/http/message.rs index 6e786c7..78b5ac4 100644 --- a/src/http/message.rs +++ b/src/http/message.rs @@ -21,7 +21,6 @@ impl TryInto for HTTPMessage { type Error = String; fn try_into(self) -> Result { let message = format!(r#"{{{}}}"#, self.build_json()); - println!("message: {}", message); match json::parse(&message) { Ok(r) => Ok(r), Err(e) => Err(format!( diff --git a/src/http/request.rs b/src/http/request.rs index d80471e..cc4388e 100644 --- a/src/http/request.rs +++ b/src/http/request.rs @@ -105,7 +105,6 @@ impl Default for HTTPStartLine { fn default() -> Self { HTTPStartLine { method: "".to_string(), - target: "".to_string(), version: HTTPVersion::Unknown, } @@ -335,7 +334,6 @@ fn test_request() { for (request, expect) in test_cases { let http_request = HTTPRequest::from(request.as_str()); - println!("{:?}", http_request); assert_eq!(expect.is_valid, http_request.is_valid()); let start_line: String = http_request.start_line.into(); diff --git a/src/http/response.rs b/src/http/response.rs index 42283d2..451e876 100644 --- a/src/http/response.rs +++ b/src/http/response.rs @@ -155,7 +155,7 @@ impl HTTPResponse { response } - /// build and HTTP 200 response with the generated JWT + /// builds an HTTP 200 response with the generated JWT pub fn send_token(token: &str) -> Self { let mut http_message = HTTPMessage::default(); http_message.put("token", token); diff --git a/src/http/router.rs b/src/http/router.rs index f89a9b0..8569553 100644 --- a/src/http/router.rs +++ b/src/http/router.rs @@ -3,9 +3,9 @@ use super::{HTTPMessage, HTTPRequest, HTTPResponse}; use crate::config::Config; +use crate::jwt::JWTSigner; 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; @@ -16,7 +16,7 @@ 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); + let mut store = FileStore::new(config.filestore_path.clone()); match &request.body { Some(ref b) => { let is_auth = store.is_auth(&b.get_data()).await; @@ -24,35 +24,20 @@ fn handle_get(request: HTTPRequest, config: Config) -> FuturePinned c, + let jwt_signer = { + match JWTSigner::new(config).await { + Ok(s) => s, 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(), - ); + let message = HTTPMessage::error(&e); 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), + match jwt_signer.sign() { + Ok(t) => HTTPResponse::send_token(&t), Err(e) => { - let message: Option = HTTPMessage::error( - format!("unable to sign the token, err={}", e).as_str(), - ); + let message = HTTPMessage::error(&e); return HTTPResponse::as_500(message); } } diff --git a/src/jwt/jwt.rs b/src/jwt/jwt.rs new file mode 100644 index 0000000..904a1fb --- /dev/null +++ b/src/jwt/jwt.rs @@ -0,0 +1,83 @@ +//! simple module to read `.pem` files and sign the token + +use crate::config::Config; +use jwt_simple::prelude::*; +use tokio::fs; + +pub struct JWTSigner { + private_key: String, + public_key: String, + issuer: String, + exp_time: u64, +} + +impl JWTSigner { + // NOTE: could be included in a Trait: `TryFrom` but difficult to handle with async + pub async fn new(config: Config) -> Result { + let mut jwt_signer = JWTSigner { + private_key: "".to_string(), + public_key: "".to_string(), + issuer: config.jwt_issuer, + exp_time: config.jwt_exp_time, + }; + + match fs::read_to_string(config.jwt_priv_key).await { + Ok(c) => { + jwt_signer.private_key = c; + } + Err(e) => { + return Err(format!("unable to read the private key, err={}", e)); + } + } + + match fs::read_to_string(config.jwt_pub_key).await { + Ok(c) => { + jwt_signer.public_key = c; + } + Err(e) => { + return Err(format!("unable to read the public key, err={}", e)); + } + } + + Ok(jwt_signer) + } + + /// builds and signs the token + pub fn sign(&self) -> Result { + let jwt_key = { + match RS384KeyPair::from_pem(&self.private_key) { + Ok(k) => k, + Err(e) => { + return Err(format!("unable to load the private key, err={}", e)); + } + } + }; + let mut claims = Claims::create(Duration::from_hours(self.exp_time)); + claims.issuer = Some(self.issuer.clone()); + + match jwt_key.sign(claims) { + Ok(token) => Ok(token), + Err(e) => { + return Err(format!("unable to sign the token, err={}", e)); + } + } + } +} + +#[tokio::test] +async fn test_signer() { + use configparser::ini::Ini; + use std::env; + + let root_path = env::var("CARGO_MANIFEST_DIR").unwrap(); + + let config_path = format!("{}/{}/{}/{}", root_path, "tests", "data", "config.ini"); + let mut config = Ini::new(); + let _r = config.load(config_path); + + let router_config = Config::try_from(config); + assert!(router_config.is_ok()); + + let jwt_signer = JWTSigner::new(router_config.unwrap()); + assert!(jwt_signer.await.is_ok()); +} diff --git a/src/jwt/mod.rs b/src/jwt/mod.rs new file mode 100644 index 0000000..051f27f --- /dev/null +++ b/src/jwt/mod.rs @@ -0,0 +1,3 @@ +mod jwt; + +pub use jwt::JWTSigner; diff --git a/src/main.rs b/src/main.rs index 62c72ce..587f34f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,5 +1,6 @@ mod config; mod http; +mod jwt; mod stores; use clap::Parser; diff --git a/tests/bash/curling.bash b/tests/bash/curling.bash index 5642cca..8fb3faa 100755 --- a/tests/bash/curling.bash +++ b/tests/bash/curling.bash @@ -6,7 +6,7 @@ # ####################################### -URL="https://dev.thegux.fr" +URL="http://localhost:9001" for i in {0..10} do diff --git a/tests/python/test_requests.py b/tests/python/test_requests.py index 19a88c2..bc7544e 100644 --- a/tests/python/test_requests.py +++ b/tests/python/test_requests.py @@ -5,7 +5,7 @@ from datetime import datetime from unittest import TestCase -URL = "https://dev.thegux.fr" +URL = "http://localhost:9001" class TestResponse(TestCase): @@ -62,7 +62,7 @@ class TestResponse(TestCase): resp = requests.post( URL + "/get/", json={"username": "tutu", "password": "titi"} ) - self.assertEqual(resp.status_code, 403, "bas status code returned") + self.assertEqual(resp.status_code, 403, "bad status code returned") self.assertIsNotNone(resp.json(), "response data must not be empty") self.assertEqual( resp.json()["error"],