mockforge_http/middleware/
security.rs

1//! Security middleware for HTTP requests
2//!
3//! This middleware emits security events for all HTTP requests, tracking access granted/denied
4//! and other security-relevant events.
5
6use axum::body::Body;
7use axum::http::{Request, Response, StatusCode};
8use axum::middleware::Next;
9use mockforge_core::security::{
10    emit_security_event, EventActor, EventOutcome, EventTarget, SecurityEvent, SecurityEventType,
11};
12use tracing::debug;
13
14/// Security middleware that emits security events for HTTP requests
15///
16/// This middleware tracks:
17/// - Access granted/denied based on response status
18/// - Request metadata (IP, user agent, method, path)
19/// - Response status codes
20pub async fn security_middleware(req: Request<Body>, next: Next) -> Response<Body> {
21    let path = req.uri().path().to_string();
22    let method = req.method().clone();
23
24    // Extract IP address and user agent
25    let ip_address = req
26        .headers()
27        .get("x-forwarded-for")
28        .or_else(|| req.headers().get("x-real-ip"))
29        .and_then(|h| h.to_str().ok())
30        .map(|s| s.to_string())
31        .or_else(|| {
32            req.extensions()
33                .get::<axum::extract::ConnectInfo<std::net::SocketAddr>>()
34                .map(|addr| addr.ip().to_string())
35        });
36
37    let user_agent = req
38        .headers()
39        .get("user-agent")
40        .and_then(|h| h.to_str().ok())
41        .map(|s| s.to_string());
42
43    // Extract user ID from request extensions (set by auth middleware)
44    // AuthClaims are wrapped in Extension by the auth middleware
45    let user_id: Option<String> = req
46        .extensions()
47        .get::<axum::extract::Extension<crate::auth::types::AuthClaims>>()
48        .and_then(|claims| claims.sub.clone());
49
50    // Process request
51    let response = next.run(req).await;
52    let status = response.status();
53
54    // Determine if access was granted or denied based on status code
55    let is_success = status.is_success();
56    let is_client_error = status.is_client_error();
57    let is_server_error = status.is_server_error();
58
59    // Emit security event based on response status
60    if is_success {
61        // Access granted
62        let event = SecurityEvent::new(SecurityEventType::AuthzAccessGranted, None, None)
63            .with_actor(EventActor {
64                user_id: user_id.clone(),
65                username: user_id.clone(),
66                ip_address: ip_address.clone(),
67                user_agent: user_agent.clone(),
68            })
69            .with_target(EventTarget {
70                resource_type: Some("api".to_string()),
71                resource_id: Some(path.clone()),
72                method: Some(method.to_string()),
73            })
74            .with_outcome(EventOutcome {
75                success: true,
76                reason: None,
77            })
78            .with_metadata("status_code".to_string(), serde_json::json!(status.as_u16()));
79        emit_security_event(event).await;
80    } else if is_client_error && status == StatusCode::FORBIDDEN {
81        // Access denied (403)
82        let event = SecurityEvent::new(SecurityEventType::AuthzAccessDenied, None, None)
83            .with_actor(EventActor {
84                user_id: user_id.clone(),
85                username: user_id.clone(),
86                ip_address: ip_address.clone(),
87                user_agent: user_agent.clone(),
88            })
89            .with_target(EventTarget {
90                resource_type: Some("api".to_string()),
91                resource_id: Some(path.clone()),
92                method: Some(method.to_string()),
93            })
94            .with_outcome(EventOutcome {
95                success: false,
96                reason: Some(format!("Access denied: {}", status)),
97            })
98            .with_metadata("status_code".to_string(), serde_json::json!(status.as_u16()));
99        emit_security_event(event).await;
100    } else if is_server_error {
101        // Server error - could indicate security issue
102        debug!("Server error detected: {} for {}", status, path);
103        // Note: We don't emit security events for all server errors, only specific ones
104        // This could be extended to detect specific security-related errors
105    }
106
107    response
108}