rbac using tonic
This commit is contained in:
@@ -2,6 +2,8 @@
|
||||
|
||||
pub mod jwt;
|
||||
pub mod middleware;
|
||||
pub mod rbac;
|
||||
|
||||
pub use jwt::*;
|
||||
pub use middleware::*;
|
||||
pub use rbac::*;
|
||||
|
||||
102
server/src/auth/logic/rbac.rs
Normal file
102
server/src/auth/logic/rbac.rs
Normal file
@@ -0,0 +1,102 @@
|
||||
// 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";
|
||||
|
||||
// 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)
|
||||
}
|
||||
Reference in New Issue
Block a user