admin_required.rs
144 lignes · 4702 octets
/// Admin Required Middleware /// Feature 009: Admin & Settings /// /// Middleware to protect routes that require admin role. /// Returns 403 Forbidden if user is not an admin. use axum::{ extract::{Request, State}, http::{StatusCode, Uri}, middleware::Next, response::{IntoResponse, Response}, }; use sea_orm::DatabaseConnection; use crate::error::AppError; use crate::middleware::auth_required::extract_user_from_jwt; /// Middleware that requires admin role /// /// Checks if the user has a valid JWT token and the "admin" role. /// If admin, allows the request to proceed. /// If not authenticated, redirects to `/login`. /// If authenticated but not admin, returns 403 Forbidden. /// /// # Arguments /// /// * `State(db)` - Database connection (for JWT validation) /// * `request` - The HTTP request (contains URI) /// * `next` - Next middleware or handler /// /// # Returns /// /// * `200 OK` if admin (passes to handler) /// * `302 Found` redirects to `/login` if not authenticated /// * `403 Forbidden` if authenticated but not admin /// /// # Example /// /// ```rust,ignore /// use axum::Router; /// use axum::routing::get; /// use axum::middleware; /// /// let admin_routes = Router::new() /// .route("/admin", get(admin_handler)) /// .route_layer(middleware::from_fn_with_state(db.clone(), admin_required)); /// ``` /// /// # Note /// /// This function signature is compatible with `axum::middleware::from_fn_with_state`. /// The URI is extracted from `request.uri()` internally. /// Returns `Response` directly (not `Result`) as required by `from_fn_with_state`. /// /// According to Axum documentation, `from_fn_with_state` provides the state to the function, /// which can be extracted using `State` extractor. The function signature should be: /// `async fn(State<S>, Request, Next) -> Response` pub async fn admin_required( State(db): State<DatabaseConnection>, request: Request, next: Next, ) -> Response { // Extract URI from request (as per Axum middleware best practices) let uri = request.uri().clone(); tracing::debug!( "admin_required middleware: checking admin access for {}", uri ); // Extract user from JWT (reuse auth_required logic) let claims = match extract_user_from_jwt(&db, &request).await { Some(claims) => claims, None => { tracing::warn!("Unauthenticated access attempt to admin route: {}", uri); return crate::middleware::auth_service::auth_failure_from_extensions( request.extensions(), &uri, ); } }; // Check if user has admin role if !claims.roles.contains(&"admin".to_string()) { tracing::warn!( "Non-admin user {} attempted to access admin route: {}", claims.sub, uri ); return (StatusCode::FORBIDDEN, "Access denied: Admin role required").into_response(); } tracing::debug!("Admin user {} accessing: {}", claims.sub, uri); // Store claims in request extensions for handlers to use let (mut parts, body) = request.into_parts(); parts.extensions.insert(claims.clone()); let request = Request::from_parts(parts, body); // User is admin, proceed to handler next.run(request).await } /// Internal implementation that takes db as parameter /// Made public for use in main.rs with from_fn closure pub async fn admin_required_impl( db: &DatabaseConnection, uri: Uri, request: Request, next: Next, ) -> Result<Response, AppError> { // Extract user from JWT let claims = match extract_user_from_jwt(db, &request).await { Some(claims) => claims, None => { tracing::warn!("Unauthenticated access attempt to admin route: {}", uri); return Ok( crate::middleware::auth_service::auth_failure_from_extensions( request.extensions(), &uri, ), ); } }; // Check if user has admin role if !claims.roles.contains(&"admin".to_string()) { tracing::warn!( "Non-admin user {} attempted to access admin route: {}", claims.sub, uri ); return Ok((StatusCode::FORBIDDEN, "Access denied: Admin role required").into_response()); } tracing::debug!("Admin user {} accessing: {}", claims.sub, uri); // Store claims in request extensions for handlers to use let (mut parts, body) = request.into_parts(); parts.extensions.insert(claims); let request = Request::from_parts(parts, body); // User is admin, proceed to handler Ok(next.run(request).await) }
GitRust