linkrs_server/http/middleware/
auth.rs1use crate::http::state::AppState;
2use crate::storage::{
3 StorageClient, StorageOperationContextOps, StorageSchemaContextOps, StorageSyncContextOps,
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
14pub const CODE_UNAUTHENTICATED: &str = "unauthenticated";
16pub const CODE_ACCOUNT_LOCKED: &str = "account_locked";
17pub const CODE_PASSWORD_CHANGE_REQUIRED: &str = "password_change_required";
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
27fn must_change_allowed(path: &str) -> bool {
33 if path == "/v1/query" || path.starts_with("/v1/query/") {
34 return true;
35 }
36 if path == "/v1/auth/me" || path == "/v1/auth/logout" {
37 return true;
38 }
39 if path.starts_with("/v1/users/") && path.ends_with("/password") {
40 return true;
41 }
42 false
43}
44
45pub async fn auth_middleware<
46 S: StorageClient
47 + StorageSchemaContextOps
48 + StorageSyncContextOps
49 + StorageOperationContextOps
50 + Clone
51 + Send
52 + Sync
53 + 'static,
54>(
55 State(state): State<AppState<S>>,
56 mut request: Request,
57 next: Next,
58) -> Response {
59 let graph_service = state.server.get_graph_service();
60
61 if graph_service.is_auth_disabled() {
67 request.extensions_mut().insert(0i64);
68 return next.run(request).await;
69 }
70
71 let session_id = match request
72 .headers()
73 .get("X-Session-ID")
74 .and_then(|h| h.to_str().ok())
75 .and_then(|s| s.parse::<i64>().ok())
76 {
77 Some(id) => id,
78 None => {
79 return deny(
80 StatusCode::UNAUTHORIZED,
81 CODE_UNAUTHENTICATED,
82 "missing or invalid session id".to_string(),
83 );
84 }
85 };
86
87 let session = match state.server.get_session_manager().find_session(session_id) {
88 Some(session) => session,
89 None => {
90 return deny(
91 StatusCode::UNAUTHORIZED,
92 CODE_UNAUTHENTICATED,
93 "session not found or expired".to_string(),
94 );
95 }
96 };
97
98 if state
99 .server
100 .get_graph_service()
101 .is_user_locked(&session.user())
102 {
103 return deny(
104 StatusCode::FORBIDDEN,
105 CODE_ACCOUNT_LOCKED,
106 format!("account {} is locked", session.user()),
107 );
108 }
109
110 if state
111 .server
112 .get_graph_service()
113 .must_change_password(&session.user())
114 && !must_change_allowed(request.uri().path())
115 {
116 return deny(
117 StatusCode::FORBIDDEN,
118 CODE_PASSWORD_CHANGE_REQUIRED,
119 "password change required before other operations".to_string(),
120 );
121 }
122
123 request.extensions_mut().insert(session_id);
124
125 next.run(request).await
126}