use crate::{errors::ApiError, state::AppState}; use axum::{extract::FromRequestParts, http::request::Parts}; use domain::value_objects::UserId; pub struct AuthUser(pub UserId); pub struct OptionalAuthUser(pub Option); impl FromRequestParts for AuthUser { type Rejection = ApiError; async fn from_request_parts(parts: &mut Parts, state: &AppState) -> Result { extract_user_id(parts, state) .await? .ok_or(ApiError::Unauthorized) .map(AuthUser) } } impl FromRequestParts for OptionalAuthUser { type Rejection = ApiError; async fn from_request_parts(parts: &mut Parts, state: &AppState) -> Result { Ok(OptionalAuthUser(extract_user_id(parts, state).await?)) } } async fn extract_user_id(parts: &mut Parts, state: &AppState) -> Result, ApiError> { if let Some(auth_header) = parts.headers.get("Authorization") { if let Ok(s) = auth_header.to_str() { if let Some(token) = s.strip_prefix("Bearer ") { return state .auth .validate_token(token) .map(Some) .map_err(|_| ApiError::Unauthorized); } } } if let Some(key_header) = parts.headers.get("X-Api-Key") { if let Ok(raw) = key_header.to_str() { let hash = sha256_hex(raw); if let Ok(Some(key)) = state.api_keys.find_by_hash(&hash).await { return Ok(Some(key.user_id)); } } } Ok(None) } fn sha256_hex(s: &str) -> String { use sha2::{Digest, Sha256}; let hash = Sha256::digest(s.as_bytes()); hex::encode(hash) }