admin_extractor.rs 70 lignes · 2270 octets
/// Admin Claims Extractor
/// Feature 009: Admin & Settings
///
/// Custom extractor that ensures the user is authenticated and has admin role.
/// Returns 403 Forbidden if user is not an admin.
///
/// This extractor reads Claims from request extensions (added by admin_required middleware).
/// The middleware must be applied to routes using this extractor.
use axum::{
    extract::FromRequestParts,
    http::request::Parts,
    response::{IntoResponse, Response},
};

use crate::auth::jwt::Claims;

/// Extract admin claims from request
///
/// This extractor ensures the user is authenticated and has admin role.
/// It reads Claims from request extensions (added by admin_required middleware).
/// If Claims are not in extensions, middleware didn't run - redirect to login.
/// If authenticated but not admin, returns 403 Forbidden.
///
/// # Usage
///
/// ```rust,ignore
/// pub async fn admin_handler(
///     AdminClaims(claims): AdminClaims,
///     State(db): State<DatabaseConnection>,
/// ) -> Result<impl IntoResponse, AppError> {
///     // Handler code
/// }
/// ```
#[derive(Clone, Debug)]
pub struct AdminClaims(pub Claims);

impl<S> FromRequestParts<S> for AdminClaims
where
    S: Send + Sync,
{
    type Rejection = Response;

    async fn from_request_parts(parts: &mut Parts, _state: &S) -> Result<Self, Self::Rejection> {
        // Get Claims from extensions (added by admin_required middleware)
        let claims = parts.extensions.get::<Claims>().ok_or_else(|| {
            // Claims absent → the auth middleware did not run for this route.
            crate::middleware::auth_service::auth_failure_from_extensions(
                &parts.extensions,
                &parts.uri,
            )
        })?;

        // Verify admin role
        if !claims.roles.contains(&"admin".to_string()) {
            let uri = parts.uri.clone();
            tracing::warn!(
                "Non-admin user {} attempted to access admin route: {}",
                claims.sub,
                uri
            );
            return Err((
                axum::http::StatusCode::FORBIDDEN,
                "Access denied: Admin role required",
            )
                .into_response());
        }

        Ok(AdminClaims(claims.clone()))
    }
}