tonic rbac to tower

This commit is contained in:
filipriec
2025-03-25 12:36:31 +01:00
parent 195375c083
commit 71ab588c16

View File

@@ -1,102 +1,36 @@
// src/auth/logic/rbac.rs
use tonic::{Request, Status};
use crate::auth::logic::jwt;
// Define roles constants to match database constraints
pub const ROLE_VIEWER: &str = "viewer";
pub const ROLE_ACCOUNTANT: &str = "accountant";
pub const ROLE_MODERATOR: &str = "moderator";
pub const ROLE_ADMIN: &str = "admin";
use tower::ServiceBuilder;
use crate::auth::logic::rbac;
// Role-check interceptor functions
pub fn require_roles<T>(roles: Vec<String>, req: Request<T>) -> Result<Request<T>, Status> {
let token = match req.metadata().get("authorization") {
Some(token) => match token.to_str() {
Ok(t) => t,
Err(_) => return Err(Status::unauthenticated("Invalid authorization header")),
},
None => return Err(Status::unauthenticated("Missing authorization header")),
};
// Strip 'Bearer ' prefix if present
let token = token.strip_prefix("Bearer ")
.unwrap_or(token);
// Validate the token and get claims
let claims = match jwt::validate_token(token) {
Ok(claims) => claims,
Err(_) => return Err(Status::unauthenticated("Invalid token")),
};
// Check if user role is allowed
if !roles.contains(&claims.role) {
tracing::warn!(
user_id = %claims.sub,
user_role = %claims.role,
required_roles = ?roles,
"Access denied: insufficient permissions"
);
return Err(Status::permission_denied("Insufficient permissions"));
}
// Add claims to request extensions for handler access
let mut req = req;
req.extensions_mut().insert(claims);
Ok(req)
}
// Convenience function for admin-only endpoints
pub fn admin_only<T>(req: Request<T>) -> Result<Request<T>, Status> {
require_roles(vec![ROLE_ADMIN.to_string()], req)
}
// Convenience function for admin and moderator endpoints
pub fn admin_moderator<T>(req: Request<T>) -> Result<Request<T>, Status> {
require_roles(
vec![
ROLE_MODERATOR.to_string(),
ROLE_ADMIN.to_string()
],
req
)
}
// Convenience function for accountants and higher (more privileges)
pub fn accountant_and_higher<T>(req: Request<T>) -> Result<Request<T>, Status> {
require_roles(
vec![
ROLE_ACCOUNTANT.to_string(),
ROLE_MODERATOR.to_string(),
ROLE_ADMIN.to_string()
],
req
)
}
// Convenience function for any authenticated user
pub fn any_authenticated_user<T>(req: Request<T>) -> Result<Request<T>, Status> {
require_roles(
vec![
ROLE_VIEWER.to_string(),
ROLE_ACCOUNTANT.to_string(),
ROLE_MODERATOR.to_string(),
ROLE_ADMIN.to_string()
],
req
)
}
// Helper to get a user's role from the request
pub fn get_user_role<T>(req: &Request<T>) -> Option<&str> {
req.extensions()
.get::<jwt::Claims>()
.map(|claims| claims.role.as_str())
}
// Helper to get a user's ID from the request
pub fn get_user_id<T>(req: &Request<T>) -> Option<uuid::Uuid> {
req.extensions()
.get::<jwt::Claims>()
.map(|claims| claims.sub)
pub async fn run_server(db_pool: sqlx::PgPool) -> Result<(), Box<dyn std::error::Error>> {
// ... existing setup code ...
// Create service layers
let adresar_layer = ServiceBuilder::new()
.layer(rbac::create_adresar_layer())
.into_inner();
let uctovnictvo_layer = ServiceBuilder::new()
.layer(rbac::create_uctovnictvo_layer())
.into_inner();
// Create services with layers
let adresar_service = AdresarServer::new(AdresarService { db_pool: db_pool.clone() })
.layer(adresar_layer);
let uctovnictvo_service = UctovnictvoServer::new(UctovnictvoService { db_pool: db_pool.clone() })
.layer(uctovnictvo_layer);
// ... repeat for other services ...
Server::builder()
.add_service(auth_server)
.add_service(adresar_service)
.add_service(uctovnictvo_service)
// ... other services ...
.serve(addr)
.await?;
Ok(())
}