From f952e82068e467e5e6099933cbb639ea6e7db546 Mon Sep 17 00:00:00 2001 From: Mehrn0ush Date: Wed, 25 Sep 2024 12:29:41 +0330 Subject: [PATCH] Refactor JWT generation to comply with RFC 7519 - Fixed type mismatch for field in struct by ensuring type. - Updated JWT generation to use the struct from and populated required fields: , , , and . - Integrated environment variable () for loading the RSA private key to enhance key management and security. - Updated to specify the path to the JWT private key. --- .env.example | 2 + src/endpoints/register.rs | 99 ++++++++++++++++++++++++++++++++-- src/jwt.rs | 21 ++++++-- src/security/access_control.rs | 1 + src/security/mod.rs | 1 + 5 files changed, 115 insertions(+), 9 deletions(-) create mode 100644 .env.example create mode 100644 src/security/access_control.rs diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..44e35a8 --- /dev/null +++ b/.env.example @@ -0,0 +1,2 @@ +JWT_PRIVATE_KEY=/path/to/your/private_key.pem + diff --git a/src/endpoints/register.rs b/src/endpoints/register.rs index 22f9987..19bc0ca 100644 --- a/src/endpoints/register.rs +++ b/src/endpoints/register.rs @@ -38,10 +38,19 @@ impl ClientStore { } } + +async fn rbac_check(token: &str, required_role: &str) -> Result<(), &'static str> { + let claims = validate_jwt(token)?; + if claims.role != required_role { + return Err("Unauthorized: insufficient role."); + } + Ok(()) +} // Register client and store metadata and secret - pub fn register_client( - &self, - metadata: ClientMetadata, + async fn register_client( + client_store: web::Data, + metadata: web::Json, + credentials: BearerAuth, ) -> Result { let client_id = Uuid::new_v4().to_string(); let client_secret = Uuid::new_v4().to_string(); @@ -67,12 +76,21 @@ impl ClientStore { } +pub fn validate_software_statement(jwt: &str) -> Result, Error> { + let decoding_key = DecodingKey::from_secret("your_secret".as_ref()); + let mut validation = Validation::new(Algorithm::HS256); + validation.validate_exp = true; // Ensure the expiration is validated + + decode::(jwt, &decoding_key, &validation) +} // The /register endpoint async fn register_client( client_store: web::Data, metadata: web::Json, + credentials: BearerAuth, + ) -> impl Responder { // Validate metadata if metadata.redirect_uris.is_empty() { @@ -127,9 +145,10 @@ async fn update_client( if let Some(existing_client) = clients.get_mut(&client_id.into_inner()) { // Update the client metadata *existing_client = updated_metadata.into_inner(); + log_registration_update(true, existing_client); return HttpResponse::Ok().body("Client updated"); } - + log_registration_update(false, &updated_metadata.into_inner()); HttpResponse::NotFound().body("Client not found") } @@ -141,3 +160,75 @@ pub fn log_registration_attempt(success: bool, metadata: &ClientMetadata) { println!("[ERROR] [{}] Failed registration attempt: {:?}", timestamp, metadata); } } +#[cfg(test)] +mod tests { + use super::*; + use actix_web::{test, App}; + + #[actix_web::test] + async fn test_register_client_success() { + let store = web::Data::new(ClientStore::new()); + let metadata = ClientMetadata { + client_name: "Test Client".to_string(), + redirect_uris: vec!["http://localhost/callback".to_string()], + grant_types: vec!["authorization_code".to_string()], + response_types: vec!["code".to_string()], + software_statement: None, + }; + + let app = test::init_service(App::new().app_data(store.clone()).route("/register", web::post().to(register_client))).await; + + let req = test::TestRequest::post().uri("/register").set_json(&metadata).to_request(); + let resp: ClientRegistrationResponse = test::call_and_read_body_json(&app, req).await; + + assert!(!resp.client_id.is_empty()); + assert!(!resp.client_secret.is_empty()); + } +} + +// The /update endpoint for updating client metadata +async fn update_client( + client_store: web::Data, + client_id: web::Path, + updated_metadata: web::Json, +) -> impl Responder { + let mut clients = client_store.clients.lock().unwrap(); + + if let Some(existing_client) = clients.get_mut(&client_id.into_inner()) { + // Update the client metadata + *existing_client = updated_metadata.into_inner(); + log_registration_update(true, existing_client); + return HttpResponse::Ok().body("Client updated successfully."); + } + + log_registration_update(false, &updated_metadata.into_inner()); + HttpResponse::NotFound().body("Client not found") +} + +// Log the update attempt +pub fn log_registration_update(success: bool, metadata: &ClientMetadata) { + if success { + println!("Client updated successfully: {:?}", metadata); + } else { + println!("Failed update attempt: {:?}", metadata); + } +} + + +// Logging successful or failed client registrations +pub fn log_registration_attempt(success: bool, metadata: &ClientMetadata) { + if success { + println!("Client registered successfully: {:?}", metadata); + } else { + println!("Failed registration attempt: {:?}", metadata); + } +} + +// Comprehensive logging including client ID and timestamp +pub fn log_audit_event(event_type: &str, client_id: &str, metadata: &ClientMetadata) { + let timestamp = chrono::Utc::now().to_rfc3339(); + println!( + "{} event at {}: Client ID: {}, Metadata: {:?}", + event_type, timestamp, client_id, metadata + ); +} diff --git a/src/jwt.rs b/src/jwt.rs index c754067..e9a6990 100644 --- a/src/jwt.rs +++ b/src/jwt.rs @@ -1,15 +1,19 @@ +use crate::core::token::Claims; use crate::error::OAuthError; +use dotenv::dotenv; use jsonwebtoken::{encode, Algorithm, EncodingKey, Header}; use serde::Serialize; +use std::env; use std::time::{Duration, SystemTime, UNIX_EPOCH}; +/* #[derive(Serialize)] struct Claims { sub: String, // The subject (client ID) scope: Vec, // Scopes for this token exp: usize, // Expiration timestamp } - +*/ /// Generates a JWT for the client credentials flow. /// /// # Arguments @@ -36,18 +40,25 @@ pub fn generate_jwt( // Create the claims (data to include in the JWT) let claims = Claims { sub: client_id, - scope: scopes.into_iter().map(|s| s.to_string()).collect(), // Convert Vec<&str> to Vec - exp: exp_unix as usize, + exp: exp_unix, + scope: Some(scopes.join(" ")), + aud: None, + client_id: None, + iat: now_unix, + iss: Some("your_issuer".to_string()), }; // Load the RSA private key from a file (this should be stored securely) - let private_key = include_bytes!("private_key.pem"); + //let private_key = include_bytes!("private_key.pem"); + let private_key = + std::env::var("JWT_PRIVATE_KEY").map_err(|_| OAuthError::TokenGenerationError)?; // Encode the JWT using RS256 algorithm, mapping any potential errors to `OAuthError::TokenGenerationError` let token = encode( &Header::new(Algorithm::RS256), &claims, - &EncodingKey::from_rsa_pem(private_key).map_err(|_| OAuthError::TokenGenerationError)?, // Map potential error loading the key + &EncodingKey::from_rsa_pem(private_key.as_bytes()) + .map_err(|_| OAuthError::TokenGenerationError)?, // Map potential error loading the key ) .map_err(|_| OAuthError::TokenGenerationError)?; // Map error from JWT encoding to OAuthError diff --git a/src/security/access_control.rs b/src/security/access_control.rs new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/src/security/access_control.rs @@ -0,0 +1 @@ + diff --git a/src/security/mod.rs b/src/security/mod.rs index 0fcc965..cde44c8 100644 --- a/src/security/mod.rs +++ b/src/security/mod.rs @@ -1,5 +1,6 @@ use crate::error::OAuthError; +pub mod access_control; pub mod csrf; pub mod encryption; pub mod mfa;