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    // Note: This would need to be adjusted based on actual auth implementation
45    let user_id: Option<String> = None; // TODO: Extract from auth middleware when available
46
47    // Process request
48    let response = next.run(req).await;
49    let status = response.status();
50
51    // Determine if access was granted or denied based on status code
52    let is_success = status.is_success();
53    let is_client_error = status.is_client_error();
54    let is_server_error = status.is_server_error();
55
56    // Emit security event based on response status
57    if is_success {
58        // Access granted
59        let event = SecurityEvent::new(SecurityEventType::AuthzAccessGranted, None, None)
60            .with_actor(EventActor {
61                user_id: user_id.clone(),
62                username: user_id.clone(),
63                ip_address: ip_address.clone(),
64                user_agent: user_agent.clone(),
65            })
66            .with_target(EventTarget {
67                resource_type: Some("api".to_string()),
68                resource_id: Some(path.clone()),
69                method: Some(method.to_string()),
70            })
71            .with_outcome(EventOutcome {
72                success: true,
73                reason: None,
74            })
75            .with_metadata("status_code".to_string(), serde_json::json!(status.as_u16()));
76        emit_security_event(event).await;
77    } else if is_client_error && status == StatusCode::FORBIDDEN {
78        // Access denied (403)
79        let event = SecurityEvent::new(SecurityEventType::AuthzAccessDenied, None, None)
80            .with_actor(EventActor {
81                user_id: user_id.clone(),
82                username: user_id.clone(),
83                ip_address: ip_address.clone(),
84                user_agent: user_agent.clone(),
85            })
86            .with_target(EventTarget {
87                resource_type: Some("api".to_string()),
88                resource_id: Some(path.clone()),
89                method: Some(method.to_string()),
90            })
91            .with_outcome(EventOutcome {
92                success: false,
93                reason: Some(format!("Access denied: {}", status)),
94            })
95            .with_metadata("status_code".to_string(), serde_json::json!(status.as_u16()));
96        emit_security_event(event).await;
97    } else if is_server_error {
98        // Server error - could indicate security issue
99        debug!("Server error detected: {} for {}", status, path);
100        // Note: We don't emit security events for all server errors, only specific ones
101        // This could be extended to detect specific security-related errors
102    }
103
104    response
105}