Skip to content

Commit

Permalink
Refactor JWT generation to comply with RFC 7519
Browse files Browse the repository at this point in the history
- 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.
  • Loading branch information
Mehrn0ush committed Sep 25, 2024
1 parent 5108f48 commit f952e82
Show file tree
Hide file tree
Showing 5 changed files with 115 additions and 9 deletions.
2 changes: 2 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
JWT_PRIVATE_KEY=/path/to/your/private_key.pem

99 changes: 95 additions & 4 deletions src/endpoints/register.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<ClientStore>,
metadata: web::Json<ClientMetadata>,
credentials: BearerAuth,
) -> Result<ClientRegistrationResponse, &'static str> {
let client_id = Uuid::new_v4().to_string();
let client_secret = Uuid::new_v4().to_string();
Expand All @@ -67,12 +76,21 @@ impl ClientStore {
}


pub fn validate_software_statement(jwt: &str) -> Result<TokenData<Claims>, 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::<Claims>(jwt, &decoding_key, &validation)
}


// The /register endpoint
async fn register_client(
client_store: web::Data<ClientStore>,
metadata: web::Json<ClientMetadata>,
credentials: BearerAuth,

) -> impl Responder {
// Validate metadata
if metadata.redirect_uris.is_empty() {
Expand Down Expand Up @@ -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")
}

Expand All @@ -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<ClientStore>,
client_id: web::Path<String>,
updated_metadata: web::Json<ClientMetadata>,
) -> 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
);
}
21 changes: 16 additions & 5 deletions src/jwt.rs
Original file line number Diff line number Diff line change
@@ -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<String>, // Scopes for this token
exp: usize, // Expiration timestamp
}

*/
/// Generates a JWT for the client credentials flow.
///
/// # Arguments
Expand All @@ -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<String>
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

Expand Down
1 change: 1 addition & 0 deletions src/security/access_control.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@

1 change: 1 addition & 0 deletions src/security/mod.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use crate::error::OAuthError;

pub mod access_control;
pub mod csrf;
pub mod encryption;
pub mod mfa;
Expand Down

0 comments on commit f952e82

Please sign in to comment.