refactor GET handler + impl JWTSigner

This commit is contained in:
landrigun 2022-10-13 16:06:27 +00:00
parent 7073a4b88e
commit 6166310283
10 changed files with 100 additions and 33 deletions

View File

@ -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;

View File

@ -21,7 +21,6 @@ impl TryInto<json::JsonValue> for HTTPMessage {
type Error = String;
fn try_into(self) -> Result<json::JsonValue, Self::Error> {
let message = format!(r#"{{{}}}"#, self.build_json());
println!("message: {}", message);
match json::parse(&message) {
Ok(r) => Ok(r),
Err(e) => Err(format!(

View File

@ -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();

View File

@ -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);

View File

@ -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<HTTPResponse>;
fn handle_get(request: HTTPRequest, config: Config) -> FuturePinned<HTTPResponse> {
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<HTTPResponse
return HTTPResponse::as_403();
}
let priv_key_content = {
match std::fs::read_to_string(config.jwt_priv_key) {
Ok(c) => 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<json::JsonValue> = 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<json::JsonValue> = HTTPMessage::error(
format!("unable to sign the token, err={}", e).as_str(),
);
let message = HTTPMessage::error(&e);
return HTTPResponse::as_500(message);
}
}

83
src/jwt/jwt.rs Normal file
View File

@ -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<Self, String> {
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<String, String> {
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());
}

3
src/jwt/mod.rs Normal file
View File

@ -0,0 +1,3 @@
mod jwt;
pub use jwt::JWTSigner;

View File

@ -1,5 +1,6 @@
mod config;
mod http;
mod jwt;
mod stores;
use clap::Parser;

View File

@ -6,7 +6,7 @@
#
#######################################
URL="https://dev.thegux.fr"
URL="http://localhost:9001"
for i in {0..10}
do

View File

@ -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"],