Skip to main content

linkrs_server/web/
middleware.rs

1//! Web Management Middleware
2//!
3//! Provides authentication and authorization middleware for web management APIs
4
5use axum::{
6    extract::{Request, State},
7    http::StatusCode,
8    middleware::Next,
9    response::{IntoResponse, Response},
10    Json,
11};
12use serde_json::json;
13
14use crate::storage::{
15    StorageClient, StorageOperationContextOps, StorageSchemaContextOps, StorageSyncContextOps,
16};
17use crate::web::WebState;
18
19fn deny(status: StatusCode, code: &str, message: String) -> Response {
20    (
21        status,
22        Json(json!({ "error": { "code": code, "message": message }, "status": status.as_u16() })),
23    )
24        .into_response()
25}
26
27/// Web authentication middleware
28///
29/// Validates the session ID from the X-Session-ID header
30/// and ensures the session is active in the session manager.
31pub async fn web_auth_middleware<
32    S: StorageClient
33        + StorageSchemaContextOps
34        + StorageSyncContextOps
35        + StorageOperationContextOps
36        + Clone
37        + Send
38        + Sync
39        + 'static,
40>(
41    State(web_state): State<WebState<S>>,
42    mut request: Request,
43    next: Next,
44) -> Response {
45    // Align with the server-wide auth-disabled contract: skip every check
46    // when enable_authorize = false or single_user_mode = true so the web
47    // console is reachable without a login round-trip on local deployments.
48    if web_state
49        .core_state
50        .server
51        .get_graph_service()
52        .is_auth_disabled()
53    {
54        request.extensions_mut().insert(0i64);
55        return next.run(request).await;
56    }
57
58    let session_id = match request
59        .headers()
60        .get("X-Session-ID")
61        .and_then(|h| h.to_str().ok())
62        .and_then(|s| s.parse::<i64>().ok())
63    {
64        Some(id) => id,
65        None => {
66            return deny(
67                StatusCode::UNAUTHORIZED,
68                "unauthenticated",
69                "missing or invalid session id".to_string(),
70            );
71        }
72    };
73
74    let session = match web_state
75        .core_state
76        .server
77        .get_session_manager()
78        .find_session(session_id)
79    {
80        Some(session) => session,
81        None => {
82            return deny(
83                StatusCode::UNAUTHORIZED,
84                "unauthenticated",
85                "session not found or expired".to_string(),
86            );
87        }
88    };
89
90    if web_state
91        .core_state
92        .server
93        .get_graph_service()
94        .is_user_locked(&session.user())
95    {
96        return deny(
97            StatusCode::FORBIDDEN,
98            "account_locked",
99            format!("account {} is locked", session.user()),
100        );
101    }
102
103    // Store session_id in request extensions for handlers to use
104    request.extensions_mut().insert(session_id);
105
106    next.run(request).await
107}