Files
imphnen-backend-service/imphnen-iam/src/v1/permissions/permissions_guard.rs
T
MythEclipse c10443f881 Add minimal test for basic hackathon operations and enhance test utilities
- Introduced a new test module for hackathon-related functionality.
- Implemented a basic test for creating a hackathon using a mock repository.
- Enhanced the test utilities in `lib.rs` for better request handling and response extraction.
- Added a `ServiceClient` struct to facilitate HTTP requests in tests.
- Created a `RequestBuilder` to streamline building and sending requests with headers and JSON bodies.
2025-10-11 10:58:51 +07:00

96 lines
2.6 KiB
Rust

use super::PermissionsEnum;
use crate::{AppState, common_response, decode_access_token, UsersRepository};
use axum::{
http::{HeaderMap, StatusCode},
response::Response, Extension,
};
use axum_extra::headers::{authorization::Bearer, Authorization, HeaderMapExt};
use surrealdb::sql::Thing;
pub async fn permissions_guard(
headers: HeaderMap,
Extension(state): Extension<AppState>,
required_permissions: Vec<PermissionsEnum>,
) -> Result<(imphnen_libs::jsonwebtoken::Claims, AppState), Response> {
let auth_header = headers
.typed_get::<Authorization<Bearer>>()
.ok_or_else(|| {
common_response(
StatusCode::UNAUTHORIZED,
"Invalid or missing authorization token",
)
})?;
let token = auth_header.token();
let claims = decode_access_token(token)
.map_err(|_| {
common_response(
StatusCode::UNAUTHORIZED,
"Invalid or expired token",
)
})?
.claims;
// Fetch user from database to get permissions. Try email first, then try using the sub as a user id.
let user_repo = UsersRepository::new(&state);
let user = match user_repo.query_user_by_email(claims.sub.clone()).await {
Ok(u) => u,
Err(_) => {
// Try treat claims.sub as a Thing id (user id)
let thing = Thing::from(("app_users".to_string(), claims.sub.clone()));
match user_repo.query_user_by_id(&thing).await {
Ok(u2) => u2,
Err(_) => {
return Err(common_response(
StatusCode::UNAUTHORIZED,
"User not found",
));
}
}
}
};
// Check permissions from database: collect both names and raw ids so checks
// succeed whether permissions are stored by name or by Thing id.
let user_permissions: Vec<String> = user
.role
.permissions
.as_ref()
.unwrap_or(&vec![])
.iter()
.filter_map(|p| p.as_ref())
.flat_map(|pp| {
let mut res: Vec<String> = Vec::new();
if let Some(name) = pp.name.clone() {
res.push(name);
}
if let Some(id) = pp.id.as_ref().map(|id| id.id.to_raw()) {
res.push(id);
}
res
})
.collect();
// If user has Administrator permission, allow all.
// Accept either the permission name or the canonical permission id.
let admin_name = PermissionsEnum::Administrator.to_string();
let admin_id = PermissionsEnum::Administrator.id();
if user_permissions.contains(&admin_name) || user_permissions.contains(&admin_id) {
return Ok((claims, state));
}
for required in &required_permissions {
let required_str = required.to_string();
if !user_permissions.contains(&required_str) {
eprintln!(" MISSING REQUIRED PERMISSION: {required_str}");
return Err(common_response(
StatusCode::FORBIDDEN,
"You don't have the required permissions",
));
}
}
Ok((claims, state))
}