From 34dafcc23e04fca0aa2070e3864c097957623594 Mon Sep 17 00:00:00 2001 From: filipriec Date: Tue, 25 Mar 2025 11:33:14 +0100 Subject: [PATCH] rbac using tonic --- server/src/auth/logic.rs | 2 + server/src/auth/logic/rbac.rs | 102 ++++++++++++++++++++++++++++++++++ 2 files changed, 104 insertions(+) create mode 100644 server/src/auth/logic/rbac.rs diff --git a/server/src/auth/logic.rs b/server/src/auth/logic.rs index 67e40b6..9a03f5e 100644 --- a/server/src/auth/logic.rs +++ b/server/src/auth/logic.rs @@ -2,6 +2,8 @@ pub mod jwt; pub mod middleware; +pub mod rbac; pub use jwt::*; pub use middleware::*; +pub use rbac::*; diff --git a/server/src/auth/logic/rbac.rs b/server/src/auth/logic/rbac.rs new file mode 100644 index 0000000..bdfa973 --- /dev/null +++ b/server/src/auth/logic/rbac.rs @@ -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(roles: Vec, req: Request) -> Result, 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(req: Request) -> Result, Status> { + require_roles(vec![ROLE_ADMIN.to_string()], req) +} + +// Convenience function for admin and moderator endpoints +pub fn admin_moderator(req: Request) -> Result, 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(req: Request) -> Result, 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(req: Request) -> Result, 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(req: &Request) -> Option<&str> { + req.extensions() + .get::() + .map(|claims| claims.role.as_str()) +} + +// Helper to get a user's ID from the request +pub fn get_user_id(req: &Request) -> Option { + req.extensions() + .get::() + .map(|claims| claims.sub) +}