//! Request extractors: authenticated user, admin user, client IP. use axum::extract::{ConnectInfo, FromRequestParts}; use axum::http::request::Parts; use axum::http::{header, StatusCode}; use axum::response::Response; use domain::user::User; use std::net::SocketAddr; use crate::error::simple; use crate::AppState; pub struct AuthUser(pub User); pub struct AdminUser(pub User); impl FromRequestParts for AuthUser { type Rejection = Response; async fn from_request_parts( parts: &mut Parts, state: &AppState, ) -> Result { let token = parts .headers .get(header::AUTHORIZATION) .and_then(|v| v.to_str().ok()) .and_then(|v| v.strip_prefix("Bearer ")) .ok_or_else(|| { simple( StatusCode::UNAUTHORIZED, "unauthorized", "missing bearer token", ) })?; state .auth .authenticate(token) .await .map(AuthUser) .map_err(|e| crate::error::ApiError(e).into_response_401()) } } impl FromRequestParts for AdminUser { type Rejection = Response; async fn from_request_parts( parts: &mut Parts, state: &AppState, ) -> Result { let AuthUser(user) = AuthUser::from_request_parts(parts, state).await?; if !user.is_admin() { return Err(simple( StatusCode::FORBIDDEN, "forbidden", "admin role required", )); } Ok(AdminUser(user)) } } impl crate::error::ApiError { /// Auth failures on protected routes are always reported as 401 (inactive users included). fn into_response_401(self) -> Response { simple( StatusCode::UNAUTHORIZED, "unauthorized", &self.0.to_string(), ) } } /// Best-effort client IP: `X-Forwarded-For` first hop, else the socket address. pub fn client_ip(parts: &Parts) -> Option { parts .headers .get("x-forwarded-for") .and_then(|v| v.to_str().ok()) .and_then(|v| v.split(',').next()) .map(|s| s.trim().to_string()) .or_else(|| { parts .extensions .get::>() .map(|c| c.0.ip().to_string()) }) } pub struct ClientIp(pub Option); impl FromRequestParts for ClientIp { type Rejection = std::convert::Infallible; async fn from_request_parts(parts: &mut Parts, _: &S) -> Result { Ok(ClientIp(client_ip(parts))) } }