api/auth/
middleware.rs

1use axum::{
2    body::Body,
3    extract::{ConnectInfo, FromRequestParts},
4    http::{Method, Request, StatusCode},
5    middleware::Next,
6    response::Response,
7};
8use axum_extra::extract::TypedHeader;
9use headers::{Origin, UserAgent};
10use std::net::SocketAddr;
11use tracing::info;
12use crate::auth::claims::AuthUser;
13
14/// Logs method, path, IP address, user ID (if authenticated), origin, and user-agent
15/// for each incoming HTTP request. Automatically skips CORS preflight `OPTIONS` requests.
16///
17/// This middleware can help trace incoming traffic, debug authentication issues,
18/// and monitor frontend usage patterns.
19///
20/// ### Usage:
21/// Apply this middleware globally using:
22///
23/// ```ignore
24/// use axum::Router;
25/// use axum::middleware::from_fn;
26/// use api::auth::middleware::log_request;
27///
28/// let app = Router::new().layer(from_fn(log_request));
29/// ```
30///
31/// ### Fields Logged:
32/// - `method`: HTTP method used (`GET`, `POST`, etc.)
33/// - `path`: Requested URI path
34/// - `ip`: Remote IP address of the client
35/// - `user`: User ID if authenticated, `0` if not
36/// - `origin`: Value of the `Origin` header if present
37/// - `user_agent`: Value of the `User-Agent` header if present
38pub async fn log_request(
39    ConnectInfo(addr): ConnectInfo<SocketAddr>,
40    req: Request<Body>,
41    next: Next,
42) -> Result<Response, StatusCode> {
43    let (mut parts, body) = req.into_parts();
44
45    // Skip logging for preflight requests
46    if parts.method == Method::OPTIONS {
47        let req = Request::from_parts(parts, body);
48        return Ok(next.run(req).await);
49    }
50
51    // Try extracting the user ID from claims
52    let user_id = AuthUser::from_request_parts(&mut parts, &())
53        .await
54        .ok()
55        .map(|AuthUser(c)| c.sub);
56
57    // Try extracting Origin and User-Agent headers
58    let origin = TypedHeader::<Origin>::from_request_parts(&mut parts, &())
59        .await
60        .ok()
61        .map(|TypedHeader(o)| o.to_string());
62
63    let user_agent = TypedHeader::<UserAgent>::from_request_parts(&mut parts, &())
64        .await
65        .ok()
66        .map(|TypedHeader(ua)| ua.to_string());
67
68    // Log relevant request metadata
69    info!(
70        method = ?parts.method,
71        path = %parts.uri.path(),
72        ip = %addr.ip(),
73        user = user_id.unwrap_or(0),
74        origin = origin.unwrap_or_else(|| "unknown".into()),
75        user_agent = user_agent.unwrap_or_else(|| "unknown".into()),
76        "Incoming request"
77    );
78
79    let req = Request::from_parts(parts, body);
80    Ok(next.run(req).await)
81}