Skip to main content

reinhardt_middleware/session/
injectable.rs

1//! `Injectable` implementation exposing `SessionData` to the DI layer.
2//!
3//! The session store is contributed to the DI container by
4//! [`SessionMiddleware::di_registrations`] both under
5//! `TypeId::of::<SessionStore>()` for direct session loading and under
6//! `FactoryOutput<SessionStoreKey, Arc<SessionStore>>` for handlers that use
7//! `#[inject] store: Depends<SessionStoreKey, Arc<SessionStore>>`.
8//! This module's `Injectable for SessionData` impl reaches into the raw
9//! singleton entry by `TypeId` to load the per-request session.
10
11use async_trait::async_trait;
12use reinhardt_di::{DiError, DiResult, Injectable, InjectionContext};
13use reinhardt_http::Request;
14
15use super::cookie::find_cookie_value;
16use super::data::SessionData;
17use super::id::{ActiveSessionId, SessionCookieName, SessionId};
18use super::store::SessionStore;
19
20/// Default session cookie name used when no `SessionCookieName` extension is present.
21const DEFAULT_SESSION_COOKIE_NAME: &str = "sessionid";
22
23/// Helper function to extract session ID from HTTP request cookies.
24///
25/// Searches for a cookie with the specified name in the Cookie header.
26///
27/// # Arguments
28///
29/// * `request` - The HTTP request to extract the session ID from
30/// * `cookie_name` - The name of the session cookie (e.g., "sessionid")
31///
32/// # Returns
33///
34/// * `Ok(String)` - The session ID if found and valid
35/// * `Err(DiError)` - If the cookie header is missing, invalid, or the session cookie is not found
36fn extract_session_id_from_request(request: &Request, cookie_name: &str) -> DiResult<String> {
37	find_cookie_value(request, cookie_name).ok_or_else(|| {
38		DiError::NotFound(format!(
39			"Session cookie '{}' not found in Cookie header",
40			cookie_name
41		))
42	})
43}
44
45#[async_trait]
46impl Injectable for SessionData {
47	async fn inject(ctx: &InjectionContext) -> DiResult<Self> {
48		// Look up the raw store under TypeId::of::<SessionStore>() — the same
49		// singleton entry SessionMiddleware::di_registrations keeps for
50		// SessionData loading. Handler code should use the keyed
51		// FactoryOutput<SessionStoreKey, Arc<SessionStore>> registration.
52		let store = ctx.get_singleton::<SessionStore>().ok_or_else(|| {
53			DiError::NotFound(
54				concat!(
55					"SessionStore not found in SingletonScope. ",
56					"Ensure SessionMiddleware is configured and its store is registered."
57				)
58				.to_string(),
59			)
60		})?;
61
62		// Get Request from context
63		let request = ctx.get_request::<Request>().ok_or_else(|| {
64			DiError::NotFound("Request not found in InjectionContext".to_string())
65		})?;
66
67		// Extract configured cookie name from request extensions.
68		// Extensions::get returns an owned value, so we extract it once and
69		// use a reference for the lookup to avoid additional allocation.
70		let ext_cookie_name = request.extensions.get::<SessionCookieName>();
71		let cookie_name = ext_cookie_name
72			.as_ref()
73			.map(|cn| cn.as_str())
74			.unwrap_or(DEFAULT_SESSION_COOKIE_NAME);
75
76		// Prefer the SessionId injected by SessionMiddleware (present for all requests,
77		// including those without a Cookie header such as the initial login request).
78		// Fall back to parsing the Cookie header for requests that bypass the middleware.
79		let session_id = if let Some(sid) = request.extensions.get::<SessionId>() {
80			sid.as_ref().to_string()
81		} else {
82			extract_session_id_from_request(&request, cookie_name)?
83		};
84
85		// Load SessionData from store, attaching the request-scoped active session
86		// ID holder so `SessionData::regenerate_id` can keep the middleware's
87		// `Set-Cookie` value in sync with rotations. See #3827.
88		let id_holder = request.extensions.get::<ActiveSessionId>();
89		let mut session = store
90			.get(&session_id)
91			.filter(|s| s.is_valid())
92			.ok_or_else(|| {
93				DiError::NotFound("Valid session not found. Session may have expired.".to_string())
94			})?;
95		session.id_holder = id_holder;
96		Ok(session)
97	}
98}