mockforge_http/auth/
middleware.rs

1//! Authentication middleware
2//!
3//! This module provides the Axum middleware for handling authentication
4//! across HTTP requests.
5
6use axum::body::Body;
7use axum::http::{Request, StatusCode};
8use axum::{extract::State, middleware::Next, response::Response};
9use tracing::{debug, error, warn};
10
11use super::authenticator::authenticate_request;
12use super::state::AuthState;
13use super::types::AuthResult;
14
15/// Authentication middleware function
16pub async fn auth_middleware(
17    State(state): State<AuthState>,
18    req: Request<Body>,
19    next: Next,
20) -> Response {
21    let path = req.uri().path().to_string();
22    let _method = req.method().clone();
23
24    // Skip authentication for health checks and admin endpoints
25    if path.starts_with("/health") || path.starts_with("/__mockforge") {
26        return next.run(req).await;
27    }
28
29    // Extract authentication information from request
30    let auth_header = req
31        .headers()
32        .get("authorization")
33        .and_then(|h| h.to_str().ok())
34        .map(|s| s.to_string());
35
36    let api_key_header = req
37        .headers()
38        .get(
39            state
40                .config
41                .api_key
42                .as_ref()
43                .map(|c| c.header_name.clone())
44                .unwrap_or_else(|| "X-API-Key".to_string()),
45        )
46        .and_then(|h| h.to_str().ok())
47        .map(|s| s.to_string());
48
49    let api_key_query = req.uri().query().and_then(|q| {
50        state
51            .config
52            .api_key
53            .as_ref()
54            .and_then(|c| c.query_name.as_ref())
55            .and_then(|param| {
56                url::form_urlencoded::parse(q.as_bytes())
57                    .find(|(k, _)| k == param)
58                    .map(|(_, v)| v.to_string())
59            })
60    });
61
62    // Try to authenticate using various methods
63    let auth_result =
64        authenticate_request(&state, &auth_header, &api_key_header, &api_key_query).await;
65
66    match auth_result {
67        AuthResult::Success(claims) => {
68            debug!("Authentication successful for user: {:?}", claims.sub);
69            // Add claims to request extensions for downstream handlers
70            let mut req = req;
71            req.extensions_mut().insert(claims);
72            next.run(req).await
73        }
74        AuthResult::Failure(reason) => {
75            warn!("Authentication failed: {}", reason);
76            let mut res = Response::new(axum::body::Body::from(
77                serde_json::json!({
78                    "error": "Authentication failed",
79                    "message": reason
80                })
81                .to_string(),
82            ));
83            *res.status_mut() = StatusCode::UNAUTHORIZED;
84            res.headers_mut().insert("www-authenticate", "Bearer".parse().unwrap());
85            res
86        }
87        AuthResult::NetworkError(reason) => {
88            error!("Authentication network error: {}", reason);
89            let mut res = Response::new(axum::body::Body::from(
90                serde_json::json!({
91                    "error": "Authentication service unavailable",
92                    "message": "Unable to verify token due to network issues"
93                })
94                .to_string(),
95            ));
96            *res.status_mut() = StatusCode::SERVICE_UNAVAILABLE;
97            res
98        }
99        AuthResult::ServerError(reason) => {
100            error!("Authentication server error: {}", reason);
101            let mut res = Response::new(axum::body::Body::from(
102                serde_json::json!({
103                    "error": "Authentication service error",
104                    "message": "Unable to verify token due to server issues"
105                })
106                .to_string(),
107            ));
108            *res.status_mut() = StatusCode::BAD_GATEWAY;
109            res
110        }
111        AuthResult::TokenExpired => {
112            warn!("Token expired");
113            let mut res = Response::new(axum::body::Body::from(
114                serde_json::json!({
115                    "error": "Token expired",
116                    "message": "The provided token has expired"
117                })
118                .to_string(),
119            ));
120            *res.status_mut() = StatusCode::UNAUTHORIZED;
121            res.headers_mut().insert(
122                "www-authenticate",
123                "Bearer error=\"invalid_token\", error_description=\"The token has expired\""
124                    .parse()
125                    .unwrap(),
126            );
127            res
128        }
129        AuthResult::TokenInvalid(reason) => {
130            warn!("Token invalid: {}", reason);
131            let mut res = Response::new(axum::body::Body::from(
132                serde_json::json!({
133                    "error": "Invalid token",
134                    "message": reason
135                })
136                .to_string(),
137            ));
138            *res.status_mut() = StatusCode::UNAUTHORIZED;
139            res.headers_mut()
140                .insert("www-authenticate", "Bearer error=\"invalid_token\"".parse().unwrap());
141            res
142        }
143        AuthResult::None => {
144            if state.config.require_auth {
145                let mut res = Response::new(axum::body::Body::from(
146                    serde_json::json!({
147                        "error": "Authentication required"
148                    })
149                    .to_string(),
150                ));
151                *res.status_mut() = StatusCode::UNAUTHORIZED;
152                res.headers_mut().insert("www-authenticate", "Bearer".parse().unwrap());
153                res
154            } else {
155                debug!("No authentication provided, proceeding without auth");
156                next.run(req).await
157            }
158        }
159    }
160}