refactor GET handler + impl JWTSigner
This commit is contained in:
parent
7073a4b88e
commit
6166310283
@ -68,13 +68,11 @@ impl Config {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: check if the file exists and rights are ok
|
|
||||||
if self.jwt_pub_key == "" {
|
if self.jwt_pub_key == "" {
|
||||||
eprintln!("invalid config parameter: JWT public key file path is empty");
|
eprintln!("invalid config parameter: JWT public key file path is empty");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: check if the file exists and rights are ok
|
|
||||||
if self.jwt_priv_key == "" {
|
if self.jwt_priv_key == "" {
|
||||||
eprintln!("invalid config parameter: JWT private key file path is empty");
|
eprintln!("invalid config parameter: JWT private key file path is empty");
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
@ -21,7 +21,6 @@ impl TryInto<json::JsonValue> for HTTPMessage {
|
|||||||
type Error = String;
|
type Error = String;
|
||||||
fn try_into(self) -> Result<json::JsonValue, Self::Error> {
|
fn try_into(self) -> Result<json::JsonValue, Self::Error> {
|
||||||
let message = format!(r#"{{{}}}"#, self.build_json());
|
let message = format!(r#"{{{}}}"#, self.build_json());
|
||||||
println!("message: {}", message);
|
|
||||||
match json::parse(&message) {
|
match json::parse(&message) {
|
||||||
Ok(r) => Ok(r),
|
Ok(r) => Ok(r),
|
||||||
Err(e) => Err(format!(
|
Err(e) => Err(format!(
|
||||||
|
|||||||
@ -105,7 +105,6 @@ impl Default for HTTPStartLine {
|
|||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
HTTPStartLine {
|
HTTPStartLine {
|
||||||
method: "".to_string(),
|
method: "".to_string(),
|
||||||
|
|
||||||
target: "".to_string(),
|
target: "".to_string(),
|
||||||
version: HTTPVersion::Unknown,
|
version: HTTPVersion::Unknown,
|
||||||
}
|
}
|
||||||
@ -335,7 +334,6 @@ fn test_request() {
|
|||||||
|
|
||||||
for (request, expect) in test_cases {
|
for (request, expect) in test_cases {
|
||||||
let http_request = HTTPRequest::from(request.as_str());
|
let http_request = HTTPRequest::from(request.as_str());
|
||||||
println!("{:?}", http_request);
|
|
||||||
assert_eq!(expect.is_valid, http_request.is_valid());
|
assert_eq!(expect.is_valid, http_request.is_valid());
|
||||||
|
|
||||||
let start_line: String = http_request.start_line.into();
|
let start_line: String = http_request.start_line.into();
|
||||||
|
|||||||
@ -155,7 +155,7 @@ impl HTTPResponse {
|
|||||||
response
|
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 {
|
pub fn send_token(token: &str) -> Self {
|
||||||
let mut http_message = HTTPMessage::default();
|
let mut http_message = HTTPMessage::default();
|
||||||
http_message.put("token", token);
|
http_message.put("token", token);
|
||||||
|
|||||||
@ -3,9 +3,9 @@
|
|||||||
|
|
||||||
use super::{HTTPMessage, HTTPRequest, HTTPResponse};
|
use super::{HTTPMessage, HTTPRequest, HTTPResponse};
|
||||||
use crate::config::Config;
|
use crate::config::Config;
|
||||||
|
use crate::jwt::JWTSigner;
|
||||||
use crate::stores::FileStore;
|
use crate::stores::FileStore;
|
||||||
use crate::stores::Store;
|
use crate::stores::Store;
|
||||||
use jwt_simple::prelude::*;
|
|
||||||
use lazy_static::lazy_static;
|
use lazy_static::lazy_static;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::future::Future;
|
use std::future::Future;
|
||||||
@ -16,7 +16,7 @@ type Handler = fn(HTTPRequest, Config) -> FuturePinned<HTTPResponse>;
|
|||||||
|
|
||||||
fn handle_get(request: HTTPRequest, config: Config) -> FuturePinned<HTTPResponse> {
|
fn handle_get(request: HTTPRequest, config: Config) -> FuturePinned<HTTPResponse> {
|
||||||
Box::pin(async move {
|
Box::pin(async move {
|
||||||
let mut store = FileStore::new(config.filestore_path);
|
let mut store = FileStore::new(config.filestore_path.clone());
|
||||||
match &request.body {
|
match &request.body {
|
||||||
Some(ref b) => {
|
Some(ref b) => {
|
||||||
let is_auth = store.is_auth(&b.get_data()).await;
|
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();
|
return HTTPResponse::as_403();
|
||||||
}
|
}
|
||||||
|
|
||||||
let priv_key_content = {
|
let jwt_signer = {
|
||||||
match std::fs::read_to_string(config.jwt_priv_key) {
|
match JWTSigner::new(config).await {
|
||||||
Ok(c) => c,
|
Ok(s) => s,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
eprintln!("error while reading JWT priv key content err={}", e);
|
let message = HTTPMessage::error(&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(),
|
|
||||||
);
|
|
||||||
return HTTPResponse::as_500(message);
|
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) {
|
match jwt_signer.sign() {
|
||||||
Ok(token) => HTTPResponse::send_token(&token),
|
Ok(t) => HTTPResponse::send_token(&t),
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
let message: Option<json::JsonValue> = HTTPMessage::error(
|
let message = HTTPMessage::error(&e);
|
||||||
format!("unable to sign the token, err={}", e).as_str(),
|
|
||||||
);
|
|
||||||
return HTTPResponse::as_500(message);
|
return HTTPResponse::as_500(message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
83
src/jwt/jwt.rs
Normal file
83
src/jwt/jwt.rs
Normal 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
3
src/jwt/mod.rs
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
mod jwt;
|
||||||
|
|
||||||
|
pub use jwt::JWTSigner;
|
||||||
@ -1,5 +1,6 @@
|
|||||||
mod config;
|
mod config;
|
||||||
mod http;
|
mod http;
|
||||||
|
mod jwt;
|
||||||
mod stores;
|
mod stores;
|
||||||
|
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
|
|||||||
@ -6,7 +6,7 @@
|
|||||||
#
|
#
|
||||||
#######################################
|
#######################################
|
||||||
|
|
||||||
URL="https://dev.thegux.fr"
|
URL="http://localhost:9001"
|
||||||
|
|
||||||
for i in {0..10}
|
for i in {0..10}
|
||||||
do
|
do
|
||||||
|
|||||||
@ -5,7 +5,7 @@ from datetime import datetime
|
|||||||
|
|
||||||
from unittest import TestCase
|
from unittest import TestCase
|
||||||
|
|
||||||
URL = "https://dev.thegux.fr"
|
URL = "http://localhost:9001"
|
||||||
|
|
||||||
|
|
||||||
class TestResponse(TestCase):
|
class TestResponse(TestCase):
|
||||||
@ -62,7 +62,7 @@ class TestResponse(TestCase):
|
|||||||
resp = requests.post(
|
resp = requests.post(
|
||||||
URL + "/get/", json={"username": "tutu", "password": "titi"}
|
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.assertIsNotNone(resp.json(), "response data must not be empty")
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
resp.json()["error"],
|
resp.json()["error"],
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user