diff --git a/.env.example b/.env.example index 6cf4c0a..8fda5ad 100644 --- a/.env.example +++ b/.env.example @@ -1,6 +1,7 @@ REDIS_URL=redis://127.0.0.1:6379 JWT_PRIVATE_KEY=/path/to/your/private_key.pem JWT_SIGNING_ALGORITHM="Dilithium" +JWT_SECRET="yoursecret" tls_key_exchange_algorithm="Kyber" CLIENT_CERT_PATH=./path/to/client_cert.pem CLIENT_KEY_PATH=./path/to/client_key.pem diff --git a/src/auth/mod.rs b/src/auth/mod.rs index 58b4398..75b43a5 100644 --- a/src/auth/mod.rs +++ b/src/auth/mod.rs @@ -3,7 +3,6 @@ use async_trait::async_trait; pub mod mock; pub mod rbac; - #[derive(Debug, Clone)] pub struct User { pub id: String, @@ -29,4 +28,3 @@ pub trait SessionManager: Send + Sync { pub trait UserAuthenticator: Send + Sync { async fn authenticate(&self, username: &str, password: &str) -> Result; } - diff --git a/src/auth/rbac.rs b/src/auth/rbac.rs index 97e9a98..1c4d8b0 100644 --- a/src/auth/rbac.rs +++ b/src/auth/rbac.rs @@ -1,17 +1,16 @@ -use jsonwebtoken::{decode, DecodingKey, Validation, Algorithm, TokenData}; -use crate::auth::mock::{MockUserAuthenticator, MockSessionManager}; -use std::collections::HashSet; +use crate::auth::mock::{MockSessionManager, MockUserAuthenticator}; +use jsonwebtoken::{decode, Algorithm, DecodingKey, TokenData, Validation}; use serde::{Deserialize, Serialize}; +use std::collections::HashSet; use std::env; - /// Struct representing the JWT claims. /// Adjust the fields based on your actual JWT structure. #[derive(Debug, Deserialize)] pub struct Claims { - pub sub: String, // Subject (user identifier) - pub exp: i64, // Expiration time as UNIX timestamp - pub roles: Vec, // Roles assigned to the user + pub sub: String, // Subject (user identifier) + pub exp: i64, // Expiration time as UNIX timestamp + pub roles: Vec, // Roles assigned to the user } /// Enum representing possible RBAC errors. @@ -27,35 +26,13 @@ pub enum RbacError { /// Result type alias for RBAC operations. pub type RbacResult = Result; -/// Performs a Role-Based Access Control (RBAC) check. -/// -/// # Arguments -/// -/// * `token` - The bearer token to validate. -/// * `required_role` - The role required to perform the action. -/// -/// # Returns -/// -/// * `Ok(())` if the token is valid and contains the required role. -/// * `Err(RbacError)` otherwise. -/// -/// # Example -/// -/// ``` -/// use crate::authentication::rbac::{rbac_check, RbacError}; -/// -/// let token = "valid_jwt_token_here"; -/// match rbac_check(token, "admin") { -/// Ok(_) => println!("Access granted"), -/// Err(e) => println!("Access denied: {:?}", e), -/// } -/// ``` pub fn rbac_check(token: &str, required_role: &str) -> RbacResult<()> { // Retrieve the JWT secret from environment variables let secret = env::var("JWT_SECRET").map_err(|_| RbacError::MissingJwtSecret)?; + println!("Using JWT_SECRET: {}", secret); // Log the secret // Define the validation parameters - let mut validation = Validation::new(Algorithm::HS256); + let mut validation = Validation::new(Algorithm::HS256); validation.validate_exp = true; // Create a HashSet of required claims @@ -68,11 +45,14 @@ pub fn rbac_check(token: &str, required_role: &str) -> RbacResult<()> { token, &DecodingKey::from_secret(secret.as_ref()), &validation, - ).map_err(|err| match *err.kind() { + ) + .map_err(|err| match *err.kind() { jsonwebtoken::errors::ErrorKind::ExpiredSignature => RbacError::ExpiredToken, _ => RbacError::InvalidToken, })?; + println!("Decoded token claims: {:?}", token_data.claims); // Log claims + // Check if the required role is present if token_data.claims.roles.contains(&required_role.to_string()) { Ok(()) @@ -81,7 +61,6 @@ pub fn rbac_check(token: &str, required_role: &str) -> RbacResult<()> { } } - /// Helper function to extract roles from a token. /// This can be used if you need to access roles beyond RBAC checks. pub fn extract_roles(token: &str) -> RbacResult> { @@ -102,7 +81,8 @@ pub fn extract_roles(token: &str) -> RbacResult> { token, &DecodingKey::from_secret(secret.as_ref()), &validation, - ).map_err(|err| match *err.kind() { + ) + .map_err(|err| match *err.kind() { jsonwebtoken::errors::ErrorKind::ExpiredSignature => RbacError::ExpiredToken, // Map additional error kinds to InvalidToken jsonwebtoken::errors::ErrorKind::InvalidToken @@ -118,11 +98,10 @@ pub fn extract_roles(token: &str) -> RbacResult> { Ok(token_data.claims.roles) } - #[cfg(test)] mod tests { use super::*; - use jsonwebtoken::{encode, Header, EncodingKey}; + use jsonwebtoken::{encode, EncodingKey, Header}; use std::env; #[derive(Debug, Serialize)] @@ -138,23 +117,20 @@ mod tests { encode(&header, &claims, &EncodingKey::from_secret(secret.as_ref())).unwrap() } - - #[test] fn test_rbac_check_invalid_token() { // Set the JWT_SECRET environment variable for testing env::set_var("JWT_SECRET", "test_secret"); - + let invalid_token = "invalid.token.value"; - + let result = rbac_check(invalid_token, "admin"); assert!(matches!(result, Err(RbacError::InvalidToken))); - + // Clean up env::remove_var("JWT_SECRET"); } - #[test] fn test_extract_roles_invalid_token() { // Set the JWT_SECRET environment variable for testing @@ -168,4 +144,65 @@ mod tests { // Clean up env::remove_var("JWT_SECRET"); } -} \ No newline at end of file + + #[test] + fn test_rbac_check_expired_token() { + dotenv::dotenv().ok(); + + // Ensure JWT_SECRET is set in the environment + let jwt_secret = env::var("JWT_SECRET").expect("JWT_SECRET must be set in .env"); + + // Set the expiration time to a timestamp in the past + let past_timestamp = (chrono::Utc::now() - chrono::Duration::seconds(3600)).timestamp(); + + // Create claims for the test token + let claims = TestClaims { + sub: "user123".to_string(), + exp: past_timestamp, // Expired token + roles: vec!["admin".to_string()], + }; + + // Generate a test token using the JWT_SECRET + let token = generate_test_token(claims, &jwt_secret); + + // Perform the RBAC check + let result = rbac_check(&token, "admin"); + + // Assert that the token is marked as expired + assert!( + matches!(result, Err(RbacError::ExpiredToken)), + "Expected ExpiredToken, but got: {:?}", + result + ); + } + #[test] + fn test_rbac_check_missing_role() { + dotenv::dotenv().ok(); // Load environment variables from .env + + // Ensure JWT_SECRET is set in the environment + let jwt_secret = env::var("JWT_SECRET").expect("JWT_SECRET must be set in .env"); + + // Define the claims with the role "user" and a valid future expiration time + let claims = TestClaims { + sub: "user123".to_string(), + exp: 9999999999, // Future expiration time + roles: vec!["user".to_string()], // Only 'user' role + }; + + // Generate the test JWT token using the JWT_SECRET + let token = generate_test_token(claims, &jwt_secret); + + // Run the RBAC check for the "admin" role + let result = rbac_check(&token, "admin"); + + // Debugging output to track the result + println!("RBAC check result: {:?}", result); + + // Ensure that the result matches the expected InsufficientRole error + assert!( + matches!(result, Err(RbacError::InsufficientRole)), + "Expected InsufficientRole, but got: {:?}", + result + ); + } +} diff --git a/src/authentication.rs b/src/authentication.rs index 05e5139..7fccc40 100644 --- a/src/authentication.rs +++ b/src/authentication.rs @@ -39,8 +39,7 @@ pub enum AuthError { InvalidCredentials, SessionNotFound, InternalError, - OAuthErrorResponse - // Add other error variants as needed + OAuthErrorResponse, // Add other error variants as needed } /* diff --git a/src/config.rs b/src/config.rs index 9949aba..4484d78 100644 --- a/src/config.rs +++ b/src/config.rs @@ -10,8 +10,10 @@ pub struct OAuthConfig { impl OAuthConfig { pub fn from_env() -> Self { let google_client_id = env::var("GOOGLE_CLIENT_ID").expect("GOOGLE_CLIENT_ID must be set"); - let google_client_secret = env::var("GOOGLE_CLIENT_SECRET").expect("GOOGLE_CLIENT_SECRET must be set"); - let google_redirect_uri = env::var("GOOGLE_REDIRECT_URI").expect("GOOGLE_REDIRECT_URI must be set"); + let google_client_secret = + env::var("GOOGLE_CLIENT_SECRET").expect("GOOGLE_CLIENT_SECRET must be set"); + let google_redirect_uri = + env::var("GOOGLE_REDIRECT_URI").expect("GOOGLE_REDIRECT_URI must be set"); OAuthConfig { google_client_id, diff --git a/src/core/client_credentials.rs b/src/core/client_credentials.rs index 074d710..bb53bd2 100644 --- a/src/core/client_credentials.rs +++ b/src/core/client_credentials.rs @@ -3,10 +3,10 @@ use crate::error::OAuthError; use crate::jwt::generate_jwt; use crate::jwt::SigningAlgorithm; use crate::storage::{ClientData, StorageBackend}; -use serde::{Deserialize, Serialize}; use rustls_pemfile::private_key; -use std::time::{Duration, SystemTime}; +use serde::{Deserialize, Serialize}; use std::sync::Arc; +use std::time::{Duration, SystemTime}; /// Validates client credentials by checking against storage (e.g., Redis, SQL). /// diff --git a/src/endpoints/client_credentials.rs b/src/endpoints/client_credentials.rs index 43178d0..1d469b1 100644 --- a/src/endpoints/client_credentials.rs +++ b/src/endpoints/client_credentials.rs @@ -1,13 +1,11 @@ -use crate::error::{OAuthError, OAuthErrorResponse }; use crate::core::client_credentials::{issue_token, validate_client_credentials, TokenResponse}; use crate::core::types::TokenRequest; +use crate::error::{OAuthError, OAuthErrorResponse}; use crate::storage::{ClientData, StorageBackend}; use actix_web::{web, HttpResponse}; use serde::Deserialize; use std::sync::Arc; - - #[derive(Deserialize)] pub struct ClientCredentialsRequest { pub grant_type: String, @@ -16,7 +14,6 @@ pub struct ClientCredentialsRequest { pub scope: Option, } - /// Handles the client credentials grant type for OAuth2. /// /// Validates client credentials and issues an access token if valid. @@ -34,7 +31,10 @@ pub async fn handle_client_credentials( storage: web::Data>, // Inject storage backend ) -> HttpResponse { // Log the incoming request (avoid logging sensitive information in production) - log::info!("Handling client credentials for client_id: {}", req.client_id); + log::info!( + "Handling client credentials for client_id: {}", + req.client_id + ); // Validate the request parameters if req.grant_type != "client_credentials" { @@ -57,7 +57,11 @@ pub async fn handle_client_credentials( .collect(); // Call core functions to validate client and issue token - match validate_client_credentials(&req.client_id, &req.client_secret, storage.as_ref().as_ref()) { + match validate_client_credentials( + &req.client_id, + &req.client_secret, + storage.as_ref().as_ref(), + ) { Ok(client) => match issue_token(&client, &scopes) { Ok(token_response) => { log::info!("Issued token for client_id: {}", req.client_id); @@ -75,4 +79,4 @@ pub async fn handle_client_credentials( HttpResponse::Unauthorized().json(error_response) } } -} \ No newline at end of file +} diff --git a/src/endpoints/delete.rs b/src/endpoints/delete.rs index c962831..14c46ab 100644 --- a/src/endpoints/delete.rs +++ b/src/endpoints/delete.rs @@ -1,13 +1,11 @@ - - +use crate::auth::rbac::rbac_check; // Adjust the path based on your project structure use crate::core::token::TokenStore; use crate::endpoints::register::Client; -use crate::auth::rbac::rbac_check; // Adjust the path based on your project structure -use actix_web::{web, HttpResponse, Responder, HttpRequest}; +use crate::endpoints::update::ClientStore; +use actix_web::{web, HttpRequest, HttpResponse, Responder}; use actix_web_httpauth::extractors::bearer::BearerAuth; use serde::{Deserialize, Serialize}; -use std::sync::RwLock; -use crate::endpoints::update::ClientStore; // Ensure the correct import path +use std::sync::RwLock; // Ensure the correct import path /// Response structure for successful client deletion. #[derive(Debug, Serialize, Deserialize)] @@ -58,11 +56,11 @@ pub async fn delete_client_handler( #[cfg(test)] mod tests { use super::*; - use actix_web::{test, App}; use crate::core::token::InMemoryTokenStore; - use std::sync::RwLock; - use crate::endpoints::register::{ClientStore, Client}; + use crate::endpoints::register::{Client, ClientStore}; + use actix_web::{test, App}; use serde_json::json; + use std::sync::RwLock; /// Helper function to create a sample client for testing. fn create_sample_client(client_id: &str) -> Client { @@ -76,5 +74,4 @@ mod tests { tbid: None, } } - } diff --git a/src/endpoints/introspection.rs b/src/endpoints/introspection.rs index 362e57f..493247d 100644 --- a/src/endpoints/introspection.rs +++ b/src/endpoints/introspection.rs @@ -4,6 +4,10 @@ use crate::core::token::{TokenGenerator, TokenRevocation}; use crate::core::types::TokenError; use crate::endpoints::introspection::token::Token; use crate::storage::TokenStore; +use actix_web::body::to_bytes; +use actix_web::{body::BoxBody, web}; +use actix_web::{test, App}; +use actix_web::{HttpResponse, Responder, ResponseError}; use async_trait::async_trait; use jsonwebtoken::TokenData; use jsonwebtoken::{Algorithm, Header}; @@ -13,14 +17,6 @@ use std::convert::TryInto; use std::sync::Arc; use std::sync::Mutex; use std::time::{Duration, SystemTime, UNIX_EPOCH}; -use actix_web::{HttpResponse, Responder, ResponseError}; -use actix_web::{web, body::BoxBody}; -use actix_web::{test, App}; -use actix_web::body::to_bytes; - - - - impl Responder for IntrospectionResponse { type Body = BoxBody; @@ -30,8 +26,6 @@ impl Responder for IntrospectionResponse { } } - - #[derive(Debug, Serialize, Deserialize)] pub struct IntrospectionRequest { pub token: String, @@ -386,229 +380,214 @@ impl TokenStore for MockTokenStore { #[cfg(test)] mod tests { use super::*; - use actix_web::{test, web, App, HttpResponse}; use actix_web::body::to_bytes; + use actix_web::{test, web, App, HttpResponse}; use serde_json::json; - - #[actix_web::test] async fn test_active_token() { - use actix_web::{test, web, App, HttpResponse}; use actix_web::body::to_bytes; + use actix_web::{test, web, App, HttpResponse}; use serde_json::json; - + // Box and coerce the mock implementations to trait objects let token_generator: Arc = Arc::new(MockTokenGeneratorintro::new()); let token_store: Arc = Arc::new(MockTokenStore::new()); - + let token = "valid_access_token"; - + let introspection_request = IntrospectionRequest { token: token.to_string(), token_type_hint: None, }; - + // Wrap the request and data appropriately let req = web::Json(introspection_request); let token_generator_data = web::Data::new(token_generator.clone()); let token_store: Arc> = Arc::new(Mutex::new(MockTokenStore::new())); let token_store_data = web::Data::new(token_store.clone()); - + // Pass the boxed trait objects to the function - let response_result = introspect_token( - req, - token_generator_data, - token_store_data, - None, - ) - .await; - + let response_result = + introspect_token(req, token_generator_data, token_store_data, None).await; + assert!(response_result.is_ok()); let response = response_result.unwrap(); - + // Extract the IntrospectionResponse from the HttpResponse let body = to_bytes(response.into_body()).await.unwrap(); let introspection_response: IntrospectionResponse = serde_json::from_slice(&body).unwrap(); - + assert_eq!(introspection_response.active, true); assert_eq!(introspection_response.client_id.unwrap(), "client_id_123"); assert_eq!(introspection_response.username.unwrap(), "user_123"); assert!(introspection_response.exp.is_some()); assert!(introspection_response.scope.is_some()); } - + #[actix_web::test] async fn test_revoked_token() { let token_generator: Arc = Arc::new(MockTokenGeneratorintro::new()); let token_store: Arc> = Arc::new(Mutex::new(MockTokenStore::new())); - + let token = "revoked_access_token"; - + // Lock the token store to mutate it { let mut token_store = token_store.lock().unwrap(); token_store.revoke_access_token(token); } - + let introspection_request = IntrospectionRequest { token: token.to_string(), token_type_hint: None, }; - + let req = web::Json(introspection_request); let token_generator_data = web::Data::new(token_generator.clone()); let token_store_data = web::Data::new(token_store.clone()); - - let response_result = introspect_token( - req, - token_generator_data, - token_store_data, - None, - ) - .await; - + + let response_result = + introspect_token(req, token_generator_data, token_store_data, None).await; + assert!(response_result.is_ok()); let response = response_result.unwrap(); - + let body = to_bytes(response.into_body()).await.unwrap(); let introspection_response: IntrospectionResponse = serde_json::from_slice(&body).unwrap(); - + assert_eq!(introspection_response.active, false); assert!(introspection_response.client_id.is_none()); assert!(introspection_response.username.is_none()); assert!(introspection_response.exp.is_none()); } - + #[tokio::test] async fn test_expired_token() { // Create the mock token generator and store let mock_token_generator = MockTokenGeneratorintro::new(); // Use concrete type first let token_store: Arc> = Arc::new(Mutex::new(MockTokenStore::new())); - + let token = "expired_access_token"; mock_token_generator.set_token_expired(token); // Now we can call this method on the concrete type - + // After using set_token_expired, we can wrap the mock_token_generator in Arc let token_generator: Arc = Arc::new(mock_token_generator); - + let introspection_request = IntrospectionRequest { token: token.to_string(), token_type_hint: None, }; - + // Wrap the request and data appropriately let req = web::Json(introspection_request); let token_generator_data = web::Data::new(token_generator.clone()); let token_store_data = web::Data::new(token_store.clone()); - + // Call introspect_token with the proper types - let response_result = introspect_token( - req, - token_generator_data, - token_store_data, - None, - ) - .await; - - assert!(response_result.is_ok(), "Expected Ok response for expired token introspection"); + let response_result = + introspect_token(req, token_generator_data, token_store_data, None).await; + + assert!( + response_result.is_ok(), + "Expected Ok response for expired token introspection" + ); let response = response_result.unwrap(); - + let body = to_bytes(response.into_body()).await.unwrap(); let introspection_response: IntrospectionResponse = serde_json::from_slice(&body).unwrap(); - - assert_eq!(introspection_response.active, false, "Expired token should be inactive"); + + assert_eq!( + introspection_response.active, false, + "Expired token should be inactive" + ); } - + //Client Authentication Tests + // These tests validate that only authorized clients can introspect tokens, and unauthorized clients are denied access. -//Client Authentication Tests -// These tests validate that only authorized clients can introspect tokens, and unauthorized clients are denied access. + // 4.1 Valid Client Credentials Test + // This test checks if the introspection succeeds when valid client credentials are provided. -// 4.1 Valid Client Credentials Test -// This test checks if the introspection succeeds when valid client credentials are provided. + #[tokio::test] + async fn test_valid_client_credentials() { + // Use Arc directly without Box + let token_generator: Arc = Arc::new(MockTokenGeneratorintro::new()); + let token_store: Arc> = Arc::new(Mutex::new(MockTokenStore::new())); -#[tokio::test] -async fn test_valid_client_credentials() { - // Use Arc directly without Box - let token_generator: Arc = Arc::new(MockTokenGeneratorintro::new()); - let token_store: Arc> = Arc::new(Mutex::new(MockTokenStore::new())); + let token = "valid_access_token"; + let client_credentials = Some(( + "valid_client_id".to_string(), + "valid_client_secret".to_string(), + )); - let token = "valid_access_token"; - let client_credentials = Some(( - "valid_client_id".to_string(), - "valid_client_secret".to_string(), - )); + let introspection_request = IntrospectionRequest { + token: token.to_string(), + token_type_hint: None, + }; - let introspection_request = IntrospectionRequest { - token: token.to_string(), - token_type_hint: None, - }; + // Wrap the request and data appropriately + let req = web::Json(introspection_request); + let token_generator_data = web::Data::new(token_generator.clone()); + let token_store_data = web::Data::new(token_store.clone()); - // Wrap the request and data appropriately - let req = web::Json(introspection_request); - let token_generator_data = web::Data::new(token_generator.clone()); - let token_store_data = web::Data::new(token_store.clone()); - - // Call introspect_token with the proper types - let response_result = introspect_token( - req, - token_generator_data, - token_store_data, - client_credentials, - ) - .await; - - assert!(response_result.is_ok()); - let response = response_result.unwrap(); - - let body = to_bytes(response.into_body()).await.unwrap(); - let introspection_response: IntrospectionResponse = serde_json::from_slice(&body).unwrap(); - - assert_eq!(introspection_response.active, true); - assert!(introspection_response.client_id.is_some()); - assert!(introspection_response.username.is_some()); -} + // Call introspect_token with the proper types + let response_result = introspect_token( + req, + token_generator_data, + token_store_data, + client_credentials, + ) + .await; + + assert!(response_result.is_ok()); + let response = response_result.unwrap(); + let body = to_bytes(response.into_body()).await.unwrap(); + let introspection_response: IntrospectionResponse = serde_json::from_slice(&body).unwrap(); -//Invalid Client Credentials Test -//This test checks if the introspection fails when invalid client credentials are provided. -#[actix_web::test] -async fn test_invalid_client_credentials() { - // Create token generator and store - let token_generator: Arc = Arc::new(MockTokenGeneratorintro::new()); - let token_store: Arc> = Arc::new(Mutex::new(MockTokenStore::new())); + assert_eq!(introspection_response.active, true); + assert!(introspection_response.client_id.is_some()); + assert!(introspection_response.username.is_some()); + } - let token = "valid_access_token"; - let client_credentials = Some(( - "invalid_client_id".to_string(), - "invalid_client_secret".to_string(), - )); + //Invalid Client Credentials Test + //This test checks if the introspection fails when invalid client credentials are provided. + #[actix_web::test] + async fn test_invalid_client_credentials() { + // Create token generator and store + let token_generator: Arc = Arc::new(MockTokenGeneratorintro::new()); + let token_store: Arc> = Arc::new(Mutex::new(MockTokenStore::new())); - let introspection_request = IntrospectionRequest { - token: token.to_string(), - token_type_hint: None, - }; + let token = "valid_access_token"; + let client_credentials = Some(( + "invalid_client_id".to_string(), + "invalid_client_secret".to_string(), + )); - let req = web::Json(introspection_request); - let token_generator_data = web::Data::new(token_generator.clone()); - let token_store_data = web::Data::new(token_store.clone()); - - let response_result = introspect_token( - req, - token_generator_data, - token_store_data, - client_credentials, - ) - .await; - - assert!(response_result.is_err()); - if let Err(TokenError::UnauthorizedClient) = response_result { - // Test passes if we receive UnauthorizedClient error - assert!(true); - } else { - assert!(false, "Expected unauthorized client error"); + let introspection_request = IntrospectionRequest { + token: token.to_string(), + token_type_hint: None, + }; + + let req = web::Json(introspection_request); + let token_generator_data = web::Data::new(token_generator.clone()); + let token_store_data = web::Data::new(token_store.clone()); + + let response_result = introspect_token( + req, + token_generator_data, + token_store_data, + client_credentials, + ) + .await; + + assert!(response_result.is_err()); + if let Err(TokenError::UnauthorizedClient) = response_result { + // Test passes if we receive UnauthorizedClient error + assert!(true); + } else { + assert!(false, "Expected unauthorized client error"); + } } } - -} \ No newline at end of file diff --git a/src/endpoints/login.rs b/src/endpoints/login.rs index 905d8d6..ef5763c 100644 --- a/src/endpoints/login.rs +++ b/src/endpoints/login.rs @@ -30,13 +30,13 @@ pub async fn login( .header("Location", "/") // Redirect to home or original URL .finish()) } - Err(AuthError::InvalidCredentials) => Ok(HttpResponse::Unauthorized().body("Invalid credentials")), + Err(AuthError::InvalidCredentials) => { + Ok(HttpResponse::Unauthorized().body("Invalid credentials")) + } Err(_) => Ok(HttpResponse::InternalServerError().body("Internal server error")), } } - - /* Notes: @@ -44,4 +44,4 @@ Notes: This login endpoint uses the UserAuthenticator and SessionManager to authenticate users and manage sessions. It sets a session_id cookie upon successful login. Users of the library can implement their own UserAuthenticator and SessionManager to customize the authentication process. -*/ \ No newline at end of file +*/ diff --git a/src/endpoints/mod.rs b/src/endpoints/mod.rs index 0696557..b48a9f7 100644 --- a/src/endpoints/mod.rs +++ b/src/endpoints/mod.rs @@ -1,18 +1,16 @@ use actix_web::web; pub mod authorize; -pub mod introspection; pub mod client_credentials; +pub mod introspection; pub mod oidc_login; pub mod register; pub mod revoke; pub mod token; pub use oidc_login::{google_callback_handler, google_login_handler}; -pub mod update; pub mod delete; pub mod login; - - +pub mod update; pub fn init_routes(cfg: &mut web::ServiceConfig) where diff --git a/src/endpoints/oidc_login.rs b/src/endpoints/oidc_login.rs index 088a6e7..5c9ee32 100644 --- a/src/endpoints/oidc_login.rs +++ b/src/endpoints/oidc_login.rs @@ -3,7 +3,6 @@ use actix_web::{web, HttpResponse, Responder}; use reqwest::Client; use serde::{Deserialize, Serialize}; - #[derive(Deserialize, Debug)] pub struct GoogleAuthCodeRequest { code: String, diff --git a/src/endpoints/register.rs b/src/endpoints/register.rs index 3405a35..3cc7857 100644 --- a/src/endpoints/register.rs +++ b/src/endpoints/register.rs @@ -4,11 +4,11 @@ use crate::security::access_control::RBAC; use actix_web::HttpRequest; use actix_web::{web, HttpResponse, Responder}; use actix_web_httpauth::extractors::bearer::BearerAuth; +use rsa::pkcs1::LineEnding; use serde::{Deserialize, Serialize}; use std::collections::HashMap; use std::sync::{Arc, RwLock}; use uuid::Uuid; -use rsa::pkcs1::LineEnding; // Structs for handling client metadata and registration responses @@ -27,8 +27,6 @@ pub struct ClientRegistrationResponse { pub client_secret: String, } - - // The Client struct to store registered client data #[derive(Debug, Clone)] pub struct Client { @@ -104,7 +102,6 @@ pub async fn register_client_handler( }) } - // Helper function to generate client ID fn generate_client_id() -> String { format!("client_{}", Uuid::new_v4()) diff --git a/src/endpoints/revoke.rs b/src/endpoints/revoke.rs index 1c00371..a9f4cd9 100644 --- a/src/endpoints/revoke.rs +++ b/src/endpoints/revoke.rs @@ -1,18 +1,17 @@ use crate::core::token::InMemoryTokenStore; use crate::core::token::TokenStore; use crate::core::types::TokenError; +use actix_web::{web, HttpResponse}; use serde::{Deserialize, Serialize}; use std::sync::{Arc, Mutex}; use std::time::{SystemTime, UNIX_EPOCH}; use warp::http::StatusCode; use warp::{reject, reply, Filter, Reply}; -use actix_web::{web, HttpResponse}; - #[derive(Debug, Deserialize, Serialize)] pub struct RevokeTokenRequest { - pub token: String, - pub token_type_hint: Option, + pub token: String, + pub token_type_hint: Option, } #[derive(Debug, Serialize)] @@ -21,9 +20,9 @@ pub struct RevokeTokenResponse { } pub async fn revoke_token_endpoint( - req: web::Json, + req: web::Json, token_store: web::Data>>, -) -> HttpResponse { +) -> HttpResponse { let mut token_store = token_store.lock().unwrap(); let token_type_hint = req.token_type_hint.as_deref(); @@ -77,4 +76,4 @@ fn get_current_time() -> Result { eprintln!("Failed to retrieve current time: {:?}", e); TokenError::InternalError }) -} \ No newline at end of file +} diff --git a/src/endpoints/update.rs b/src/endpoints/update.rs index 5bfd8c1..2e59c1c 100644 --- a/src/endpoints/update.rs +++ b/src/endpoints/update.rs @@ -7,11 +7,8 @@ use serde::{Deserialize, Serialize}; use std::collections::HashMap; use std::sync::RwLock; - - // Structs for Update requests and responses - #[derive(Debug, Clone, Serialize, Deserialize)] pub struct ClientUpdateRequest { pub client_name: Option, @@ -99,8 +96,6 @@ pub async fn update_client_handler( } } - - // RBAC check mock function for testing pub fn rbac_check(token: &str, required_role: &str) -> Result<(), &'static str> { // Mock implementation, replace with actual RBAC logic @@ -116,42 +111,40 @@ pub fn rbac_check(token: &str, required_role: &str) -> Result<(), &'static str> #[cfg(test)] mod tests { use super::*; - use actix_web::{test, App}; - use std::sync::RwLock; use crate::core::token::InMemoryTokenStore; use crate::endpoints::register; use crate::endpoints::register::register_client_handler; use crate::endpoints::register::ClientMetadata; use crate::endpoints::register::ClientRegistrationResponse; + use actix_web::{test, App}; + use std::sync::RwLock; - -#[actix_web::test] -async fn test_update_client_not_found() { - let store = web::Data::new(RwLock::new(ClientStore::new(InMemoryTokenStore::new()))); - - let update_metadata = ClientUpdateRequest { - client_name: Some("Non-Existent Client".to_string()), - redirect_uris: None, - grant_types: None, - response_types: None, - software_statement: None, - }; - - let app = test::init_service(App::new() - .app_data(store.clone()) - .route("/update/{client_id}", web::put().to(update_client_handler::)) - ).await; - - let update_req = test::TestRequest::put() - .uri("/update/non_existent_client_id") - .insert_header(("Authorization", "Bearer valid_admin_token")) - .set_json(&update_metadata) - .to_request(); - - let resp = test::call_service(&app, update_req).await; - - assert_eq!(resp.status(), 404); + #[actix_web::test] + async fn test_update_client_not_found() { + let store = web::Data::new(RwLock::new(ClientStore::new(InMemoryTokenStore::new()))); + + let update_metadata = ClientUpdateRequest { + client_name: Some("Non-Existent Client".to_string()), + redirect_uris: None, + grant_types: None, + response_types: None, + software_statement: None, + }; + + let app = test::init_service(App::new().app_data(store.clone()).route( + "/update/{client_id}", + web::put().to(update_client_handler::), + )) + .await; + + let update_req = test::TestRequest::put() + .uri("/update/non_existent_client_id") + .insert_header(("Authorization", "Bearer valid_admin_token")) + .set_json(&update_metadata) + .to_request(); + + let resp = test::call_service(&app, update_req).await; + + assert_eq!(resp.status(), 404); + } } - - -} \ No newline at end of file diff --git a/src/error.rs b/src/error.rs index 82f9f37..36ca31b 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,6 +1,5 @@ use serde::{Deserialize, Serialize}; - #[derive(Debug)] pub enum TokenError { InvalidToken, @@ -24,9 +23,6 @@ pub enum OAuthError { InvalidToken, } - - - /// Struct representing an OAuth2 error response. #[derive(Debug, Serialize, Deserialize)] pub struct OAuthErrorResponse { @@ -53,8 +49,6 @@ impl OAuthErrorResponse { } } - - impl From for OAuthErrorResponse { fn from(err: OAuthError) -> Self { match err { @@ -98,21 +92,17 @@ impl From for OAuthErrorResponse { Some("Too many requests have been made in a given amount of time."), None, ), - OAuthError::InternalError(desc) => OAuthErrorResponse::new( - "server_error", - Some(&desc), - None, - ), + OAuthError::InternalError(desc) => { + OAuthErrorResponse::new("server_error", Some(&desc), None) + } OAuthError::InvalidCredentials => OAuthErrorResponse::new( "invalid_credentials", Some("The provided credentials are invalid."), None, ), - OAuthError::SessionNotFound => OAuthErrorResponse::new( - "session_not_found", - Some("No active session found."), - None, - ), + OAuthError::SessionNotFound => { + OAuthErrorResponse::new("session_not_found", Some("No active session found."), None) + } OAuthError::InvalidToken => OAuthErrorResponse::new( "invalid_token", Some("The token provided is invalid."), @@ -121,4 +111,4 @@ impl From for OAuthErrorResponse { // Handle other error variants accordingly } } -} \ No newline at end of file +} diff --git a/src/lib.rs b/src/lib.rs index aa29664..39fa80a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,38 +1,35 @@ +use crate::auth::mock::{MockSessionManager, MockUserAuthenticator}; +use crate::config::OAuthConfig; use crate::core::authorization::AuthorizationCodeFlow; use crate::core::authorization::MockTokenGenerator; +use crate::core::token::{InMemoryTokenStore, RedisTokenStore}; +use crate::endpoints::register::ClientStore; +use crate::routes::init_routes; use crate::storage::memory::MemoryCodeStore; +use actix_web::{web, App, HttpServer}; use security::tls::configure_tls; use std::sync::{Arc, Mutex}; use std::time::Duration; -use crate::config::OAuthConfig; -use actix_web::{web, App, HttpServer}; -use crate::routes::init_routes; -use crate::core::token::{InMemoryTokenStore, RedisTokenStore}; -use crate::endpoints::register::ClientStore; -use crate::auth::mock::{MockUserAuthenticator, MockSessionManager}; - use std::sync::RwLock; - -pub mod core; +pub mod auth; +pub mod auth_middleware; pub mod authentication; +pub mod config; +pub mod core; pub mod endpoints; pub mod error; pub mod jwt; pub mod routes; pub mod security; pub mod storage; -pub mod config; -pub mod auth_middleware; -pub mod auth; pub mod oidc { - pub mod jwks; pub mod claims; pub mod discovery; + pub mod jwks; } - // Public function to expose TLS setup as part of the library's API pub fn setup_tls() -> rustls::ClientConfig { configure_tls() @@ -58,8 +55,6 @@ pub fn create_auth_code_flow() -> Arc> { Arc::new(Mutex::new(auth_code_flow)) } - - #[actix_web::main] async fn main() -> std::io::Result<()> { // Load configuration @@ -79,9 +74,11 @@ async fn main() -> std::io::Result<()> { .app_data(client_store.clone()) .app_data(web::Data::new(authenticator.clone())) .app_data(web::Data::new(session_manager.clone())) - .configure(init_routes::) // Initialize all routes + .configure( + init_routes::, + ) // Initialize all routes }) .bind(("127.0.0.1", 8080))? .run() .await -} \ No newline at end of file +} diff --git a/src/oidc/jwks.rs b/src/oidc/jwks.rs index d7c957b..f42b9f4 100644 --- a/src/oidc/jwks.rs +++ b/src/oidc/jwks.rs @@ -9,13 +9,11 @@ use rsa::pkcs1::LineEnding; use serde::{Deserialize, Serialize}; use std::env; - #[derive(Debug, Deserialize)] pub struct Jwks { pub keys: Vec, } - #[derive(Deserialize, Debug)] pub struct Jwk { pub kty: String, diff --git a/src/routes/mod.rs b/src/routes/mod.rs index 6a60f3e..490ad47 100644 --- a/src/routes/mod.rs +++ b/src/routes/mod.rs @@ -1,35 +1,25 @@ pub mod auth; pub mod device_flow; pub mod users; -use actix_web::{web, HttpResponse}; -use crate::endpoints::register::register_client_handler; -use crate::endpoints::update::update_client_handler; -use crate::auth::{UserAuthenticator, SessionManager}; -use crate::endpoints::token::token_endpoint; -use crate::endpoints::delete::delete_client_handler; +use crate::auth::{SessionManager, UserAuthenticator}; use crate::endpoints::authorize::authorize; +use crate::endpoints::delete::delete_client_handler; use crate::endpoints::introspection::introspect_token; +use crate::endpoints::register::register_client_handler; use crate::endpoints::revoke::revoke_token_endpoint; +use crate::endpoints::token::token_endpoint; +use crate::endpoints::update::update_client_handler; use crate::InMemoryTokenStore; - +use actix_web::{web, HttpResponse}; pub fn init_routes(cfg: &mut web::ServiceConfig) where A: 'static + UserAuthenticator, S: 'static + SessionManager, { - cfg.service( - web::resource("/authorize") - .route(web::get().to(authorize)), - ); - cfg.service( - web::resource("/device/code") - .route(web::post().to(device_flow::device_authorize)), - ); - cfg.service( - web::resource("/device/token") - .route(web::post().to(device_flow::device_token)), - ); + cfg.service(web::resource("/authorize").route(web::get().to(authorize))); + cfg.service(web::resource("/device/code").route(web::post().to(device_flow::device_authorize))); + cfg.service(web::resource("/device/token").route(web::post().to(device_flow::device_token))); cfg.service( web::resource("/register") @@ -44,16 +34,7 @@ where .route(web::delete().to(delete_client_handler::)), ); // Register other endpoints similarly... - cfg.service( - web::resource("/token") - .route(web::post().to(token_endpoint)), - ); - cfg.service( - web::resource("/introspection") - .route(web::post().to(introspect_token)), - ); - cfg.service( - web::resource("/revoke") - .route(web::post().to(revoke_token_endpoint)), - ); + cfg.service(web::resource("/token").route(web::post().to(token_endpoint))); + cfg.service(web::resource("/introspection").route(web::post().to(introspect_token))); + cfg.service(web::resource("/revoke").route(web::post().to(revoke_token_endpoint))); } diff --git a/src/storage/backend.rs b/src/storage/backend.rs index b0ec943..2643e4b 100644 --- a/src/storage/backend.rs +++ b/src/storage/backend.rs @@ -1,4 +1,3 @@ - use async_trait::async_trait; #[async_trait] diff --git a/src/storage/memory.rs b/src/storage/memory.rs index 7a312ae..d08bb13 100644 --- a/src/storage/memory.rs +++ b/src/storage/memory.rs @@ -297,10 +297,6 @@ fn generate_new_token() -> String { "new_refresh_token_placeholder".to_string() } - - - - #[cfg(test)] mod tests { use super::*; diff --git a/src/storage/mod.rs b/src/storage/mod.rs index 872ba7e..c363884 100644 --- a/src/storage/mod.rs +++ b/src/storage/mod.rs @@ -2,8 +2,8 @@ pub mod memory; pub mod redis; pub mod sql; use crate::error::OAuthError; -pub mod client; pub mod backend; +pub mod client; pub use memory::{CodeStore, TokenStore};