ferrox_guards/lib.rs
1//! # Ferrox Guards (`ferrox-guards`)
2//!
3//! `ferrox-guards` provides declarative role-based access control (RBAC) extractors (e.g. `RequireRole`, `RequirePermission`)
4//! for protecting Axum endpoints in Ferrox applications.
5//!
6//! ## Key Features
7//! - 🛡️ **Declarative Guard Extractors**: Protect handlers with compile-safe role constraints.
8//! - 🔒 **PASETO/JWT Integration**: Inspects authenticated user claims directly from request extensions.
9
10pub mod squeezer;
11pub use squeezer::FeatureSqueezer;
12pub mod session_replay_guard;
13pub use session_replay_guard::{ClientFingerprint, SessionReplayDetector, SessionStatus};
14pub mod unbypassable_enforcer;
15pub use unbypassable_enforcer::{MandatoryComplianceEnforcer, mandatory_compliance_middleware};
16
17use axum::{
18 async_trait,
19 extract::FromRequestParts,
20 http::{request::Parts, StatusCode, header},
21};
22use ferrox_security::PasetoAuth;
23use ferrox_errors::AppError;
24
25pub struct RequireRole(pub String);
26
27#[async_trait]
28impl<S> FromRequestParts<S> for RequireRole
29where
30 S: Send + Sync,
31{
32 type Rejection = AppError;
33
34 async fn from_request_parts(parts: &mut Parts, _state: &S) -> Result<Self, Self::Rejection> {
35 // Extract Authorization header
36 let auth_header = parts.headers.get(header::AUTHORIZATION)
37 .and_then(|value| value.to_str().ok())
38 .ok_or_else(|| AppError::Unauthorized("Missing Authorization header".into()))?;
39
40 if !auth_header.starts_with("Bearer ") {
41 return Err(AppError::Unauthorized("Invalid token format".into()));
42 }
43
44 let token = &auth_header[7..];
45
46 // In a real implementation, you would pass the secret via State or env.
47 // For boilerplate, we'll assume a dummy secret or validation logic.
48 // let auth = PasetoAuth::new(Secret::new("...".into())).unwrap();
49 // let claims = auth.validate_token(token)?;
50
51 // Let's simulate role extraction from claims
52 let role = "admin"; // Simulated
53
54 // The exact required role is usually checked via a parameter in Axum.
55 // Since Rust traits don't support const generics for strings yet easily in extractors,
56 // we extract the role and the controller checks it.
57 // Or we use this Extractor just to get the Role string.
58
59 Ok(RequireRole(role.to_string()))
60 }
61}