use axum::extract::FromRequestParts; use axum::http::request::Parts; use domain::{DomainError, User}; use crate::errors::AppError; use crate::state::AppState; pub struct CurrentUser(pub User); impl FromRequestParts for CurrentUser { type Rejection = AppError; async fn from_request_parts( parts: &mut Parts, state: &AppState, ) -> Result { #[cfg(feature = "auth-jwt")] { return match try_jwt_auth(parts, state).await { Ok(user) => Ok(CurrentUser(user)), Err(e) => Err(e), }; } #[cfg(not(feature = "auth-jwt"))] { let _ = (parts, state); Err(AppError(DomainError::Unauthenticated( "No authentication backend configured".to_string(), ))) } } } pub struct OptionalCurrentUser(pub Option); impl FromRequestParts for OptionalCurrentUser { type Rejection = AppError; async fn from_request_parts( parts: &mut Parts, state: &AppState, ) -> Result { #[cfg(feature = "auth-jwt")] { if let Ok(user) = try_jwt_auth(parts, state).await { return Ok(OptionalCurrentUser(Some(user))); } let query_token = parts.uri.query().and_then(|q| { q.split('&') .find_map(|seg| seg.strip_prefix("token=")) .map(|v| v.to_owned()) }); if let Some(token) = query_token { let user = validate_jwt_token(&token, state).await.ok(); return Ok(OptionalCurrentUser(user)); } Ok(OptionalCurrentUser(None)) } #[cfg(not(feature = "auth-jwt"))] { let _ = (parts, state); Ok(OptionalCurrentUser(None)) } } } pub struct AdminUser(pub User); impl FromRequestParts for AdminUser { type Rejection = AppError; async fn from_request_parts( parts: &mut Parts, state: &AppState, ) -> Result { let CurrentUser(user) = CurrentUser::from_request_parts(parts, state).await?; if !user.is_admin() { return Err(AppError(DomainError::Forbidden("Admin access required".to_string()))); } Ok(AdminUser(user)) } } #[cfg(feature = "auth-jwt")] async fn try_jwt_auth(parts: &mut Parts, state: &AppState) -> Result { use axum::http::header::AUTHORIZATION; let auth_header = parts .headers .get(AUTHORIZATION) .ok_or_else(|| AppError(DomainError::Unauthenticated("Missing Authorization header".to_string())))?; let auth_str = auth_header .to_str() .map_err(|_| AppError(DomainError::Unauthenticated("Invalid Authorization header encoding".to_string())))?; let token = auth_str.strip_prefix("Bearer ").ok_or_else(|| { AppError(DomainError::Unauthenticated("Authorization header must use Bearer scheme".to_string())) })?; validate_jwt_token(token, state).await } #[cfg(feature = "auth-jwt")] pub(crate) async fn validate_jwt_token(token: &str, state: &AppState) -> Result { let validator = state .jwt_validator .as_ref() .ok_or_else(|| AppError(DomainError::InfrastructureError("JWT validator not configured".to_string())))?; let claims = validator.validate_access_token(token).map_err(|e| { tracing::debug!("JWT validation failed: {:?}", e); match e { adapter_auth::JwtError::Expired => { AppError(DomainError::Unauthenticated("Token expired".to_string())) } adapter_auth::JwtError::InvalidFormat => { AppError(DomainError::Unauthenticated("Invalid token format".to_string())) } _ => AppError(DomainError::Unauthenticated("Token validation failed".to_string())), } })?; let user_id: uuid::Uuid = claims .sub .parse() .map_err(|_| AppError(DomainError::Unauthenticated("Invalid user ID in token".to_string())))?; let user = state .auth_deps .user_query .find_by_id(domain::UserId::from(user_id)) .await .map_err(|e| AppError(DomainError::InfrastructureError(format!("Failed to fetch user: {}", e))))? .ok_or_else(|| AppError(DomainError::Unauthenticated("User not found".to_string())))?; Ok(user) }