Merge branch 'feature/impl-jwt' into develop
This commit is contained in:
		
						commit
						8d448719da
					
				
							
								
								
									
										873
									
								
								Cargo.lock
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										873
									
								
								Cargo.lock
									
									
									
										generated
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @ -11,10 +11,19 @@ lazy_static = "1.4.0" | |||||||
| regex = "1" | regex = "1" | ||||||
| tokio = { version = "1.21.1", features = ["full"] } | tokio = { version = "1.21.1", features = ["full"] } | ||||||
| async-trait = "0.1.57" | async-trait = "0.1.57" | ||||||
|  | jwt-simple = "0.11.1" | ||||||
| 
 | 
 | ||||||
| # useful for tests (embedded files should be delete in release ?) | # useful for tests (embedded files should be delete in release ?) | ||||||
| #rust-embed="6.4.1" | #rust-embed="6.4.1" | ||||||
| 
 | 
 | ||||||
|  | [dependencies.configparser] | ||||||
|  | version = "3.0.2" | ||||||
|  | features = ["indexmap"] | ||||||
|  | 
 | ||||||
|  | [dependencies.clap] | ||||||
|  | version = "3.2" | ||||||
|  | features = ["derive"] | ||||||
|  | 
 | ||||||
| [dependencies.async-std] | [dependencies.async-std] | ||||||
| version = "1.6" | version = "1.6" | ||||||
| features = ["attributes"] | features = ["attributes"] | ||||||
|  | |||||||
							
								
								
									
										47
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										47
									
								
								README.md
									
									
									
									
									
								
							| @ -2,15 +2,52 @@ | |||||||
| 
 | 
 | ||||||
| A little web server providing JWT token for auth auser. | A little web server providing JWT token for auth auser. | ||||||
| 
 | 
 | ||||||
| **NOTE**: for now, the server is listening on port **9000**. Change it, in the src if needed. |  | ||||||
| 
 |  | ||||||
| ## Build | ## Build | ||||||
| 
 |  | ||||||
| ```bash | ```bash | ||||||
| cargo build --release | cargo build --release | ||||||
|  | ``` | ||||||
| 
 | 
 | ||||||
| # run the server | ## Configuration | ||||||
| ./target/release/simple-auth | 
 | ||||||
|  | ### Store | ||||||
|  | The store represents the credentials. For now, this a `.txt` file with plain passwords. You have to create one like: | ||||||
|  | ```txt | ||||||
|  | # acts as a comment (only on a start line) | ||||||
|  | <username>:<password> | ||||||
|  | ``` | ||||||
|  | **WARN**: the file should have a chmod to **600**. | ||||||
|  | 
 | ||||||
|  | ### RSA key pair creation | ||||||
|  | The server uses **RS384** signature algorithm (asymmetric). You have to create a private key to sign the token and a public key for the validation: | ||||||
|  | ```bash | ||||||
|  | openssl genrsa -out priv.pem 2048 | ||||||
|  | openssl rsa -in priv.pem -outform PEM -pubout -out pub.pem | ||||||
|  | ``` | ||||||
|  | **WARN**: those files must be readable be the server user. | ||||||
|  | 
 | ||||||
|  | ### INI file | ||||||
|  | To start the server correctly, you need to create an `.ini` file as below: | ||||||
|  | ```ini | ||||||
|  | [server] | ||||||
|  | url = <ip>:<port> | ||||||
|  | 
 | ||||||
|  | [store] | ||||||
|  | path = <store_path> | ||||||
|  | 
 | ||||||
|  | [jwt] | ||||||
|  | issuer = <issuer.fr> | ||||||
|  | private_key = <priv_key_path>  | ||||||
|  | public_key = <pub_key_path> | ||||||
|  | expiration_time = 2 # in hours | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | ## Run | ||||||
|  | ```bash | ||||||
|  | ./simple-auth <ini_path> | ||||||
|  | 
 | ||||||
|  | curl http://<ip>:<port>/get/ -d '{"username":"<user>", "password":"<password>"}' | ||||||
|  | # should returned | ||||||
|  | {"token":"<header>.<payload>.<signature>"} | ||||||
| ``` | ``` | ||||||
| 
 | 
 | ||||||
| ## Test | ## Test | ||||||
|  | |||||||
| @ -6,4 +6,4 @@ pub mod router; | |||||||
| 
 | 
 | ||||||
| pub use request::HTTPRequest; | pub use request::HTTPRequest; | ||||||
| pub use response::{HTTPResponse, HTTPStatusCode}; | pub use response::{HTTPResponse, HTTPStatusCode}; | ||||||
| pub use router::ROUTER; | pub use router::{Config, ROUTER}; | ||||||
|  | |||||||
| @ -206,6 +206,7 @@ impl HTTPRequest { | |||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     #[allow(dead_code)] | ||||||
|     pub fn is_valid(&self) -> bool { |     pub fn is_valid(&self) -> bool { | ||||||
|         return self.start_line.is_valid(); |         return self.start_line.is_valid(); | ||||||
|     } |     } | ||||||
|  | |||||||
| @ -11,6 +11,7 @@ pub enum HTTPStatusCode { | |||||||
|     Http400, |     Http400, | ||||||
|     Http403, |     Http403, | ||||||
|     Http404, |     Http404, | ||||||
|  |     Http500, | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| impl Into<String> for HTTPStatusCode { | impl Into<String> for HTTPStatusCode { | ||||||
| @ -20,6 +21,7 @@ impl Into<String> for HTTPStatusCode { | |||||||
|             Self::Http400 => "400".to_string(), |             Self::Http400 => "400".to_string(), | ||||||
|             Self::Http404 => "404".to_string(), |             Self::Http404 => "404".to_string(), | ||||||
|             Self::Http403 => "403".to_string(), |             Self::Http403 => "403".to_string(), | ||||||
|  |             Self::Http500 => "500".to_string(), | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| } | } | ||||||
| @ -51,6 +53,7 @@ impl HTTPStatusLine { | |||||||
|         self.status_code = code; |         self.status_code = code; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     #[allow(dead_code)] | ||||||
|     pub fn get_status_code(&self) -> HTTPStatusCode { |     pub fn get_status_code(&self) -> HTTPStatusCode { | ||||||
|         self.status_code.clone() |         self.status_code.clone() | ||||||
|     } |     } | ||||||
| @ -89,6 +92,17 @@ impl Into<String> for HTTPResponse { | |||||||
| } | } | ||||||
| 
 | 
 | ||||||
| impl HTTPResponse { | impl HTTPResponse { | ||||||
|  |     pub fn as_500() -> Self { | ||||||
|  |         let mut response = Self::default(); | ||||||
|  | 
 | ||||||
|  |         response | ||||||
|  |             .status_line | ||||||
|  |             .set_status_code(HTTPStatusCode::Http500); | ||||||
|  |         response.body = json::parse(r#"{"error": "unexpected error occurred"}"#).unwrap(); | ||||||
|  | 
 | ||||||
|  |         response | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     pub fn as_404() -> Self { |     pub fn as_404() -> Self { | ||||||
|         let mut response = Self::default(); |         let mut response = Self::default(); | ||||||
| 
 | 
 | ||||||
| @ -117,16 +131,14 @@ impl HTTPResponse { | |||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     // TODO: need to be adjust to accept `json::JsonValue`
 |     // TODO: need to be adjust to accept `json::JsonValue`
 | ||||||
|     pub fn as_200() -> Self { |     pub fn as_200(token: String) -> Self { | ||||||
|         let mut response = Self::default(); |         let mut response = Self::default(); | ||||||
| 
 | 
 | ||||||
|         response |         response | ||||||
|             .status_line |             .status_line | ||||||
|             .set_status_code(HTTPStatusCode::Http200); |             .set_status_code(HTTPStatusCode::Http200); | ||||||
|         response.body = json::parse( | 
 | ||||||
|             r#"{"token": "header.payload.signature", "refresh": "header.payload.signature"}"#, |         response.body = json::parse(format!(r#"{{"token": "{}"}}"#, token).as_str()).unwrap(); | ||||||
|         ) |  | ||||||
|         .unwrap(); |  | ||||||
| 
 | 
 | ||||||
|         response |         response | ||||||
|     } |     } | ||||||
|  | |||||||
| @ -1,28 +1,146 @@ | |||||||
| //! router aims to handle correctly the request corresponding to the target
 | //! router aims to handle correctly the request corresponding to the target
 | ||||||
| //! it implements all the logic to build an `HTTPResponse`
 | //! it implements all the logic to build an `HTTPResponse`
 | ||||||
| 
 | 
 | ||||||
| use super::{HTTPRequest, HTTPResponse, HTTPStatusCode}; | use super::{HTTPRequest, HTTPResponse}; | ||||||
| use crate::stores::FileStore; | use crate::stores::FileStore; | ||||||
| use crate::stores::Store; | use crate::stores::Store; | ||||||
|  | use configparser::ini::Ini; | ||||||
|  | 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; | ||||||
| use std::pin::Pin; | use std::pin::Pin; | ||||||
|  | use std::str::FromStr; | ||||||
| 
 | 
 | ||||||
| type FuturePinned<HTTPResponse> = Pin<Box<dyn Future<Output = HTTPResponse>>>; | type FuturePinned<HTTPResponse> = Pin<Box<dyn Future<Output = HTTPResponse>>>; | ||||||
| type Handler = fn(HTTPRequest) -> FuturePinned<HTTPResponse>; | type Handler = fn(HTTPRequest, Config) -> FuturePinned<HTTPResponse>; | ||||||
| 
 | 
 | ||||||
| fn handle_get(request: HTTPRequest) -> FuturePinned<HTTPResponse> { | #[derive(Clone)] | ||||||
|  | pub struct Config { | ||||||
|  |     jwt_exp_time: u64, | ||||||
|  |     jwt_issuer: String, | ||||||
|  |     jwt_priv_key: String, | ||||||
|  |     jwt_pub_key: String, | ||||||
|  |     filestore_path: String, | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | impl Default for Config { | ||||||
|  |     fn default() -> Self { | ||||||
|  |         Config { | ||||||
|  |             jwt_exp_time: 0, | ||||||
|  |             jwt_issuer: "".to_string(), | ||||||
|  |             jwt_priv_key: "".to_string(), | ||||||
|  |             jwt_pub_key: "".to_string(), | ||||||
|  |             filestore_path: "".to_string(), | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | impl TryFrom<Ini> for Config { | ||||||
|  |     type Error = String; | ||||||
|  |     fn try_from(config: Ini) -> Result<Self, Self::Error> { | ||||||
|  |         let exp_time = config | ||||||
|  |             .get("jwt", "expiration_time") | ||||||
|  |             .unwrap_or("".to_string()); | ||||||
|  |         let jwt_exp_time = { | ||||||
|  |             match u64::from_str(&exp_time) { | ||||||
|  |                 Ok(v) => v, | ||||||
|  |                 Err(e) => { | ||||||
|  |                     eprintln!("unable to convert JWT expiration time into u64 err={}", e); | ||||||
|  |                     0 | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         }; | ||||||
|  |         let config = Config { | ||||||
|  |             jwt_exp_time, | ||||||
|  |             jwt_issuer: config.get("jwt", "issuer").unwrap_or("".to_string()), | ||||||
|  |             jwt_pub_key: config.get("jwt", "public_key").unwrap_or("".to_string()), | ||||||
|  |             jwt_priv_key: config.get("jwt", "private_key").unwrap_or("".to_string()), | ||||||
|  |             filestore_path: config.get("store", "path").unwrap_or("".to_string()), | ||||||
|  |         }; | ||||||
|  | 
 | ||||||
|  |         if !config.validate() { | ||||||
|  |             return Err("ini file configuration validation failed".to_string()); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         Ok(config) | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | impl Config { | ||||||
|  |     /// validates config ini file
 | ||||||
|  |     fn validate(&self) -> bool { | ||||||
|  |         if self.jwt_exp_time <= 0 { | ||||||
|  |             eprintln!("invalid config parameter: JWT expiration time is negative or equals to 0"); | ||||||
|  |             return false; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         if self.jwt_issuer == "" { | ||||||
|  |             eprintln!("invalid config parameter: JWT issuer is empty"); | ||||||
|  |             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; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         if self.filestore_path == "" { | ||||||
|  |             eprintln!("invalid config parameter: filestore path is empty"); | ||||||
|  |             return false; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         true | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | fn handle_get(request: HTTPRequest, config: Config) -> FuturePinned<HTTPResponse> { | ||||||
|     Box::pin(async move { |     Box::pin(async move { | ||||||
|         // TODO: path to `store.txt` must not be hardcoded, should be in a config file and load at runtime
 |         let mut store = FileStore::new(config.filestore_path); | ||||||
|         let mut store = FileStore::new("tests/data/store.txt".to_string()); |  | ||||||
|         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; | ||||||
|                 if !is_auth { |                 if !is_auth { | ||||||
|                     return HTTPResponse::as_403(); |                     return HTTPResponse::as_403(); | ||||||
|                 } |                 } | ||||||
|                 HTTPResponse::as_200() | 
 | ||||||
|  |                 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, | ||||||
|  |                         // TODO: set error in the message body
 | ||||||
|  |                         Err(e) => { | ||||||
|  |                             eprintln!("error occurred while getting private key err={}", e); | ||||||
|  |                             return HTTPResponse::as_500(); | ||||||
|  |                         } | ||||||
|  |                     } | ||||||
|  |                 }; | ||||||
|  |                 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::as_200(token), | ||||||
|  |                     // TODO: set the error in the message body
 | ||||||
|  |                     Err(e) => { | ||||||
|  |                         eprintln!("error occurred while signing the token err={}", e); | ||||||
|  |                         return HTTPResponse::as_500(); | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|             } |             } | ||||||
|             None => HTTPResponse::as_400(), |             None => HTTPResponse::as_400(), | ||||||
|         } |         } | ||||||
| @ -31,12 +149,12 @@ fn handle_get(request: HTTPRequest) -> FuturePinned<HTTPResponse> { | |||||||
| 
 | 
 | ||||||
| /// validates the token by checking:
 | /// validates the token by checking:
 | ||||||
| /// * expiration time
 | /// * expiration time
 | ||||||
| fn handle_validate(request: HTTPRequest) -> FuturePinned<HTTPResponse> { | fn handle_validate(request: HTTPRequest, _config: Config) -> FuturePinned<HTTPResponse> { | ||||||
|     Box::pin(async move { |     Box::pin(async move { | ||||||
|         match &request.body { |         match &request.body { | ||||||
|             Some(ref _b) => { |             Some(ref _b) => { | ||||||
|                 // TODO: impl the JWT validation
 |                 // TODO: impl the JWT validation
 | ||||||
|                 HTTPResponse::as_200() |                 HTTPResponse::as_200("header.payload.signature".to_string()) | ||||||
|             } |             } | ||||||
|             None => HTTPResponse::as_400(), |             None => HTTPResponse::as_400(), | ||||||
|         } |         } | ||||||
| @ -59,12 +177,12 @@ lazy_static! { | |||||||
| pub struct Router; | pub struct Router; | ||||||
| 
 | 
 | ||||||
| impl Router { | impl Router { | ||||||
|     pub async fn route(&self, request_str: &str) -> HTTPResponse { |     pub async fn route(&self, request_str: &str, config: Config) -> HTTPResponse { | ||||||
|         let request = HTTPRequest::from(request_str); |         let request = HTTPRequest::from(request_str); | ||||||
|         let target = request.start_line.get_target(); |         let target = request.start_line.get_target(); | ||||||
| 
 | 
 | ||||||
|         match HTTP_METHODS.get(target.as_str()) { |         match HTTP_METHODS.get(target.as_str()) { | ||||||
|             Some(f) => f(request).await, |             Some(f) => f(request, config).await, | ||||||
|             None => HTTPResponse::as_404(), |             None => HTTPResponse::as_404(), | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| @ -75,12 +193,59 @@ pub const ROUTER: Router = Router {}; | |||||||
| 
 | 
 | ||||||
| #[tokio::test] | #[tokio::test] | ||||||
| async fn test_route() { | async fn test_route() { | ||||||
|  |     use super::HTTPStatusCode; | ||||||
|  | 
 | ||||||
|     let router: &Router = &ROUTER; |     let router: &Router = &ROUTER; | ||||||
|  |     let config: Config = Config::default(); | ||||||
|     let request_str = "POST /get/ HTTP/1.1\r\n\r\n"; |     let request_str = "POST /get/ HTTP/1.1\r\n\r\n"; | ||||||
| 
 | 
 | ||||||
|     let response: HTTPResponse = router.route(request_str).await; |     let response: HTTPResponse = router.route(request_str, config).await; | ||||||
|     assert_eq!( |     assert_eq!( | ||||||
|         HTTPStatusCode::Http400, |         HTTPStatusCode::Http400, | ||||||
|         response.status_line.get_status_code() |         response.status_line.get_status_code() | ||||||
|     ); |     ); | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | #[test] | ||||||
|  | fn test_config() { | ||||||
|  |     use std::env; | ||||||
|  | 
 | ||||||
|  |     let root_path = env::var("CARGO_MANIFEST_DIR").unwrap(); | ||||||
|  | 
 | ||||||
|  |     // TODO: path::Path should be better
 | ||||||
|  |     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()); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | #[test] | ||||||
|  | fn test_bad_config() { | ||||||
|  |     use std::env; | ||||||
|  | 
 | ||||||
|  |     let root_path = env::var("CARGO_MANIFEST_DIR").unwrap(); | ||||||
|  | 
 | ||||||
|  |     // TODO: path::Path should be better
 | ||||||
|  |     let config_path = format!("{}/{}/{}/{}", root_path, "tests", "data", "bad_config.ini"); | ||||||
|  |     let mut config = Ini::new(); | ||||||
|  |     let _r = config.load(config_path); | ||||||
|  | 
 | ||||||
|  |     let router_config = Config::try_from(config); | ||||||
|  |     assert!(router_config.is_err()); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | #[test] | ||||||
|  | fn test_bad_config_path() { | ||||||
|  |     use std::env; | ||||||
|  | 
 | ||||||
|  |     let root_path = env::var("CARGO_MANIFEST_DIR").unwrap(); | ||||||
|  | 
 | ||||||
|  |     // TODO: path::Path should be better
 | ||||||
|  |     let config_path = format!("{}/{}/{}/{}", root_path, "tests", "data", "con.ini"); | ||||||
|  |     let mut config = Ini::new(); | ||||||
|  | 
 | ||||||
|  |     let result = config.load(config_path); | ||||||
|  |     assert!(result.is_err()); | ||||||
|  | } | ||||||
|  | |||||||
							
								
								
									
										49
									
								
								src/main.rs
									
									
									
									
									
								
							
							
						
						
									
										49
									
								
								src/main.rs
									
									
									
									
									
								
							| @ -1,33 +1,68 @@ | |||||||
| mod http; | mod http; | ||||||
| mod stores; | mod stores; | ||||||
| 
 | 
 | ||||||
|  | use clap::Parser; | ||||||
|  | use configparser::ini::Ini; | ||||||
| use tokio::{ | use tokio::{ | ||||||
|     io::{AsyncReadExt, AsyncWriteExt}, |     io::{AsyncReadExt, AsyncWriteExt}, | ||||||
|     net::{TcpListener, TcpStream}, |     net::{TcpListener, TcpStream}, | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| use http::ROUTER; | use http::{Config, ROUTER}; | ||||||
| 
 | 
 | ||||||
| const SERVER_URL: &str = "127.0.0.1:9000"; | #[derive(Parser)] | ||||||
|  | #[clap(author, version, about, long_about = None)] | ||||||
|  | struct Cli { | ||||||
|  |     /// config filepath (.ini)
 | ||||||
|  |     config: String, | ||||||
|  | } | ||||||
| 
 | 
 | ||||||
| #[tokio::main] | #[tokio::main] | ||||||
| async fn main() { | async fn main() { | ||||||
|     let listener = TcpListener::bind(SERVER_URL).await.unwrap(); |     let args = Cli::parse(); | ||||||
|     println!("server is listening on '{}'", SERVER_URL); | 
 | ||||||
|  |     let mut config = Ini::new(); | ||||||
|  |     match config.load(args.config) { | ||||||
|  |         Ok(c) => c, | ||||||
|  |         Err(e) => { | ||||||
|  |             eprintln!("error while loading the config file, err={}", e); | ||||||
|  |             std::process::exit(1); | ||||||
|  |         } | ||||||
|  |     }; | ||||||
|  | 
 | ||||||
|  |     let server_url = config.get("server", "url").unwrap_or("".to_string()); | ||||||
|  |     let listener = { | ||||||
|  |         match TcpListener::bind(&server_url).await { | ||||||
|  |             Ok(t) => { | ||||||
|  |                 println!("server is listening on '{}'", server_url); | ||||||
|  |                 t | ||||||
|  |             } | ||||||
|  |             Err(e) => { | ||||||
|  |                 eprintln!("error occurred while initializing tcp listener err={}", e); | ||||||
|  |                 std::process::exit(1); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     }; | ||||||
|  | 
 | ||||||
|  |     let router_config: Config = if let Ok(c) = Config::try_from(config) { | ||||||
|  |         c | ||||||
|  |     } else { | ||||||
|  |         std::process::exit(1); | ||||||
|  |     }; | ||||||
| 
 | 
 | ||||||
|     loop { |     loop { | ||||||
|         let (stream, _) = listener.accept().await.unwrap(); |         let (stream, _) = listener.accept().await.unwrap(); | ||||||
|         handle_connection(stream).await; |         handle_connection(stream, router_config.clone()).await; | ||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| /// parses the incoming request (partial spec implementation) and build an HTTP response
 | /// parses the incoming request (partial spec implementation) and build an HTTP response
 | ||||||
| async fn handle_connection(mut stream: TcpStream) { | async fn handle_connection(mut stream: TcpStream, config: Config) { | ||||||
|     let mut buffer: [u8; 1024] = [0; 1024]; |     let mut buffer: [u8; 1024] = [0; 1024]; | ||||||
|     let n = stream.read(&mut buffer).await.unwrap(); |     let n = stream.read(&mut buffer).await.unwrap(); | ||||||
| 
 | 
 | ||||||
|     let request_string = std::str::from_utf8(&buffer[0..n]).unwrap(); |     let request_string = std::str::from_utf8(&buffer[0..n]).unwrap(); | ||||||
|     let response = ROUTER.route(request_string).await; |     let response = ROUTER.route(request_string, config).await; | ||||||
|     let response_str: String = response.into(); |     let response_str: String = response.into(); | ||||||
| 
 | 
 | ||||||
|     stream.write(response_str.as_bytes()).await.unwrap(); |     stream.write(response_str.as_bytes()).await.unwrap(); | ||||||
|  | |||||||
| @ -1,5 +1,8 @@ | |||||||
|  | import jwt | ||||||
| import requests | import requests | ||||||
| 
 | 
 | ||||||
|  | from datetime import datetime | ||||||
|  | 
 | ||||||
| from unittest import TestCase | from unittest import TestCase | ||||||
| 
 | 
 | ||||||
| URL = "https://dev.thegux.fr" | URL = "https://dev.thegux.fr" | ||||||
| @ -12,9 +15,15 @@ class TestResponse(TestCase): | |||||||
|         ) |         ) | ||||||
|         self.assertEqual(resp.status_code, 200, "bad status code returned") |         self.assertEqual(resp.status_code, 200, "bad status code returned") | ||||||
|         self.assertIsNotNone(resp.json(), "response data can't be empty") |         self.assertIsNotNone(resp.json(), "response data can't be empty") | ||||||
|         self.assertEqual( | 
 | ||||||
|             resp.json()["token"], "header.payload.signature", "bad status returned" |         token = resp.json()["token"] | ||||||
|         ) |         jwt_decoded = jwt.decode(token, options={"verify_signature": False}) | ||||||
|  |         self.assertEqual("thegux.fr", jwt_decoded["iss"]) | ||||||
|  | 
 | ||||||
|  |         jwt_exp = datetime.fromtimestamp(jwt_decoded["exp"]) | ||||||
|  |         jwt_iat = datetime.fromtimestamp(jwt_decoded["iat"]) | ||||||
|  |         date_exp = datetime.strptime(str(jwt_exp - jwt_iat), "%H:%M:%S") | ||||||
|  |         self.assertEqual(2, date_exp.hour) | ||||||
| 
 | 
 | ||||||
|     def test_validate_target(self): |     def test_validate_target(self): | ||||||
|         resp = requests.post( |         resp = requests.post( | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user