admin_helper.rs 126 lignes · 4182 octets
/// Admin Helper Functions
/// Feature 009: Admin & Settings
///
/// Helper functions to extract and validate admin claims from requests.
/// These can be used directly in handlers when middleware cannot be applied.
use axum::extract::Request;
use axum::http::request::Parts;
use sea_orm::DatabaseConnection;

use crate::auth::jwt::Claims;
use crate::auth::jwt::JwtService;
use crate::auth::jwt_extractor;
use crate::config::auth::AuthConfig;
use crate::error::AppError;
use crate::middleware::auth_required::extract_user_from_jwt;
use crate::services::jwt_blacklist_service::JwtBlacklistService;

/// Extract admin claims from request parts and headers
///
/// This function extracts and validates admin claims from request parts.
/// It can be used directly in handlers when middleware cannot be applied.
///
/// # Arguments
///
/// * `db` - Database connection
/// * `parts` - Request parts (contains headers, URI, etc.)
///
/// # Returns
///
/// * `Ok(Claims)` if user is authenticated and has admin role
/// * `Err(AppError::Unauthorized)` if not authenticated
/// * `Err(AppError::Forbidden)` if authenticated but not admin
pub async fn extract_admin_claims_from_parts(
    db: &DatabaseConnection,
    parts: &Parts,
) -> Result<Claims, AppError> {
    // Extract JWT from headers/cookies
    let jwt_token = jwt_extractor::extract_jwt_from_request_parts(parts)?;

    // Check if token is blacklisted
    if JwtBlacklistService::is_blacklisted(db, &jwt_token)
        .await
        .is_ok_and(|b| b)
    {
        tracing::warn!("Blacklisted JWT token used for admin route");
        return Err(AppError::Unauthorized);
    }

    // Validate JWT token — use the shared AuthConfig from request extensions
    // when present, otherwise fall back to `AuthConfig::from_env()` (same
    // regression mitigation as `AuthService`/`AdminService` since commit
    // `d4d6828`).
    let auth_config = match parts
        .extensions
        .get::<std::sync::Arc<AuthConfig>>()
        .cloned()
    {
        Some(cfg) => cfg,
        None => match AuthConfig::from_env() {
            Ok(cfg) => std::sync::Arc::new(cfg),
            Err(err) => {
                tracing::error!(
                    "admin_helper: AuthConfig unavailable (no extension and from_env failed): {err}"
                );
                return Err(AppError::Config(
                    "Auth configuration not available".to_string(),
                ));
            }
        },
    };

    let claims = JwtService::validate_token(&auth_config, &jwt_token).map_err(|_| {
        tracing::warn!("Invalid JWT token for admin route");
        AppError::Unauthorized
    })?;

    // 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
        );
        return Err(AppError::Forbidden);
    }

    tracing::debug!("Admin user {} accessing admin route", claims.sub);
    Ok(claims)
}

/// Extract admin claims from request
///
/// This function extracts and validates admin claims from a request.
/// It can be used directly in handlers when middleware cannot be applied.
///
/// # Arguments
///
/// * `db` - Database connection
/// * `request` - The HTTP request
///
/// # Returns
///
/// * `Ok(Claims)` if user is authenticated and has admin role
/// * `Err(AppError::Unauthorized)` if not authenticated
/// * `Err(AppError::Forbidden)` if authenticated but not admin
pub async fn extract_admin_claims(
    db: &DatabaseConnection,
    request: &Request,
) -> Result<Claims, AppError> {
    // Extract user from JWT
    let claims = extract_user_from_jwt(db, request).await.ok_or_else(|| {
        tracing::warn!("Unauthenticated access attempt to admin route");
        AppError::Unauthorized
    })?;

    // 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
        );
        return Err(AppError::Forbidden);
    }

    tracing::debug!("Admin user {} accessing admin route", claims.sub);
    Ok(claims)
}