use axum::{ extract::{Request, State}, http::StatusCode, middleware::Next, response::Response, }; use crate::config::AppState; use crate::auth::Claims; /// Middleware to check if user has permission for a resource /// /// This middleware checks JWT claims (attached by auth middleware) /// and verifies the user has the required permission level. /// /// # Permission Levels /// - "read": Can view resource /// - "write": Can modify resource /// - "admin": Full control including deletion pub async fn has_permission( State(state): State, required_permission: String, request: Request, next: Next, ) -> Result { // Extract user_id from JWT claims (attached by auth middleware) let user_id = match request.extensions().get::() { Some(claims) => claims.sub.clone(), None => return Err(StatusCode::UNAUTHORIZED), }; // Extract resource_id from URL path let resource_id = match extract_resource_id(request.uri().path()) { Some(id) => id, None => return Err(StatusCode::BAD_REQUEST), }; // Check if user has the required permission (either directly or through shares) let has_perm = match state.db .check_permission(&user_id, &resource_id, &required_permission) .await { Ok(allowed) => allowed, Err(_) => return Err(StatusCode::INTERNAL_SERVER_ERROR), }; if !has_perm { return Err(StatusCode::FORBIDDEN); } Ok(next.run(request).await) } /// Extract resource ID from URL path /// /// # Examples /// - /api/shares/123 -> Some("123") /// - /api/users/me/profile -> None fn extract_resource_id(path: &str) -> Option { let segments: Vec<&str> = path.split('/').collect(); // Look for ID segment after a resource type // e.g., /api/shares/:id for (i, segment) in segments.iter().enumerate() { if segment == &"shares" || segment == &"permissions" { if i + 1 < segments.len() { let id = segments[i + 1]; if !id.is_empty() { return Some(id.to_string()); } } } } None } #[cfg(test)] mod tests { use super::*; #[test] fn test_extract_resource_id() { assert_eq!( extract_resource_id("/api/shares/123"), Some("123".to_string()) ); assert_eq!( extract_resource_id("/api/shares/abc-123"), Some("abc-123".to_string()) ); assert_eq!( extract_resource_id("/api/users/me"), None ); } }