// Unused use super::context::CurrentUser; use super::error::HTTPError; use crate::core::AppState; use crate::permissions::{ChannelPermission, ServerPermission}; use axum::extract::FromRequestParts; use axum::http::request::Parts; use std::ops::Deref; use uuid::Uuid; /// An Axum extractor that ensures the currently authenticated user has the specified /// server permission(s) on a target server. /// /// The target `server_id` is automatically extracted from path parameters (supporting /// path parameters named `server_id` or `id`). /// /// # Superuser Bypass /// If the user is a superuser (`is_superuser == true`), the permission check automatically passes. /// /// # Usage Example /// ```rust /// use axum::extract::State; /// use uuid::Uuid; /// use crate::http::permissions::RequireServerPermission; /// use crate::permissions::ServerPermission; /// use crate::core::AppState; /// /// pub async fn update_server_settings( /// RequireServerPermission::<{ ServerPermission::MANAGE_SERVER.bits() }>(user): RequireServerPermission<{ ServerPermission::MANAGE_SERVER.bits() }>, /// State(state): State, /// Path(server_id): Path, /// ) -> Result<(), HTTPError> { /// // User has MANAGE_SERVER or is a superuser /// Ok(()) /// } /// ``` #[derive(Clone, Debug)] pub struct RequireServerPermission(pub CurrentUser); impl Deref for RequireServerPermission { type Target = CurrentUser; fn deref(&self) -> &Self::Target { &self.0 } } impl FromRequestParts for RequireServerPermission where S: Send + Sync, { type Rejection = HTTPError; async fn from_request_parts(parts: &mut Parts, state: &S) -> Result { // 1. Extract CurrentUser (which validates authentication and returns 401 if missing) let current_user = CurrentUser::from_request_parts(parts, state).await?; // 2. Superuser bypasses all checks if current_user.is_superuser { return Ok(RequireServerPermission(current_user)); } // 3. Get AppState from extensions let app_state = match parts.extensions.get::() { Some(s) => s.clone(), None => { return Err(HTTPError::InternalServerError( "AppState missing in request extensions".to_string(), )); } }; // 4. Extract server_id from path parameters. let server_id = match extract_path_param_uuid(parts, &["server_id", "id"]) { Some(id) => id, None => { return Err(HTTPError::BadRequest( "Missing or invalid server_id".to_string(), )); } }; // 5. Check user permission via server repository let permission_result = app_state .repositories .server .get_user_permission(server_id, current_user.id) .await; let permission_bits = match permission_result { Ok(Some(p)) => p.permissions, Ok(None) => 0, Err(e) => return Err(HTTPError::InternalServerError(e.to_string())), }; let required = ServerPermission::from_bits_truncate(PERM); let granted = ServerPermission::from_bits_truncate(permission_bits as u64); if granted.contains(required) { Ok(RequireServerPermission(current_user)) } else { Err(HTTPError::Forbidden) } } } /// An Axum extractor that ensures the currently authenticated user has the specified /// channel permission(s) on a target channel. /// /// The target `channel_id` (or `id`) is automatically extracted from path parameters. /// /// # Superuser Bypass /// If the user is a superuser (`is_superuser == true`), the permission check automatically passes. /// /// # Usage Example /// ```rust /// use axum::extract::State; /// use uuid::Uuid; /// use crate::http::permissions::RequireChannelPermission; /// use crate::permissions::ChannelPermission; /// use crate::core::AppState; /// /// pub async fn read_channel_messages( /// RequireChannelPermission::<{ ChannelPermission::READ_CHANNEL.bits() }>(user): RequireChannelPermission<{ ChannelPermission::READ_CHANNEL.bits() }>, /// State(state): State, /// Path(channel_id): Path, /// ) -> Result<(), HTTPError> { /// // User has READ_CHANNEL or is a superuser /// Ok(()) /// } /// ``` #[derive(Clone, Debug)] pub struct RequireChannelPermission(pub CurrentUser); impl Deref for RequireChannelPermission { type Target = CurrentUser; fn deref(&self) -> &Self::Target { &self.0 } } impl FromRequestParts for RequireChannelPermission where S: Send + Sync, { type Rejection = HTTPError; async fn from_request_parts(parts: &mut Parts, state: &S) -> Result { let current_user = CurrentUser::from_request_parts(parts, state).await?; if current_user.is_superuser { return Ok(RequireChannelPermission(current_user)); } let app_state = match parts.extensions.get::() { Some(s) => s.clone(), None => { return Err(HTTPError::InternalServerError( "AppState missing in request extensions".to_string(), )); } }; let channel_id = match extract_path_param_uuid(parts, &["channel_id", "id"]) { Some(id) => id, None => { return Err(HTTPError::BadRequest( "Missing or invalid channel_id".to_string(), )); } }; let permission_result = app_state .repositories .channel .get_user_permission(channel_id, current_user.id) .await; let permission_bits = match permission_result { Ok(Some(p)) => p.permissions, Ok(None) => 0, Err(e) => return Err(HTTPError::InternalServerError(e.to_string())), }; let required = ChannelPermission::from_bits_truncate(PERM); let granted = ChannelPermission::from_bits_truncate(permission_bits as u64); if granted.contains(required) { Ok(RequireChannelPermission(current_user)) } else { Err(HTTPError::Forbidden) } } } /// Helper function to extract a Uuid path parameter matching any of the given key names /// from Axum request extensions. fn extract_path_param_uuid(parts: &Parts, keys: &[&str]) -> Option { if let Some(map) = parts .extensions .get::>() { for key in keys { if let Some(val) = map.get(*key) { if let Ok(uuid) = Uuid::parse_str(val) { return Some(uuid); } } } } if let Some(params) = parts.extensions.get::>() { for (k, v) in params { if keys.contains(&k.as_str()) { if let Ok(uuid) = Uuid::parse_str(v) { return Some(uuid); } } } } None }