Skip to main content

alibi_core/session/cookie_cache/runtime/
mod.rs

1//! Request-local cache cookies and genuine stored/cached snapshot transitions.
2mod cookies;
3mod issuance;
4mod read;
5
6use super::{CacheValidation, CacheVersionContext};
7use crate::session::SessionRequest;
8use crate::types::RequestExtensions;
9use crate::utils::cookie_utils::{related_cookie_name, sign_cookie_value, verify_cookie_value};
10use crate::{AuthContext, AuthError, AuthRequest, AuthResult, AuthSchema, AuthSession, AuthUser};
11pub(super) use cookies::browser_preference;
12use cookies::cache_value;
13pub(super) use cookies::chunk_index;
14pub use cookies::chunked_cookie_headers;
15pub use cookies::chunked_cookie_value;
16use cookies::cookie_values;
17use cookies::cookies;
18use cookies::existing_names;
19pub use cookies::session_cleanup_headers;
20use indexmap::IndexMap;
21use issuance::build_headers;
22pub use issuance::emit_issuance;
23pub use issuance::emit_issuance_in_transaction;
24pub use issuance::emit_issuance_snapshot;
25pub use issuance::stored_headers;
26use issuance::stored_read_headers;
27pub use issuance::take_issuance;
28pub use read::authenticated;
29pub use read::read;
30pub(crate) use read::renew_cache;
31pub(crate) use read::{CacheDecoding, authenticated_with};
32use std::sync::{Arc, Mutex};
33
34#[derive(Debug)]
35struct IssuancePreference(bool);
36
37/// Metadata from the authenticated cache snapshot retained by get-session
38/// response hooks. Nested middleware publishes its completed session instead.
39#[derive(Clone, Debug)]
40pub struct SessionHookCacheMetadata {
41    pub updated_at: f64,
42    pub version: Option<String>,
43}
44
45#[derive(Debug)]
46struct SessionHookCache(Option<SessionHookCacheMetadata>);
47
48#[derive(Debug)]
49struct PublishedSession(Option<PublishedSessionSnapshot>);
50
51/// One completed issuance with actual retained callback-stage output.
52/// Trusted hooks may observe it; it never establishes authentication.
53#[derive(Clone, Debug)]
54pub struct PublishedSessionSnapshot {
55    user: crate::UserView,
56    session: crate::SessionView,
57    user_output: Option<crate::AdapterOutput>,
58    session_output: Option<crate::AdapterOutput>,
59}
60impl PublishedSessionSnapshot {
61    #[must_use]
62    pub const fn user(&self) -> &crate::UserView {
63        &self.user
64    }
65    #[must_use]
66    pub const fn session(&self) -> &crate::SessionView {
67        &self.session
68    }
69    #[must_use]
70    pub const fn user_output(&self) -> Option<&crate::AdapterOutput> {
71        self.user_output.as_ref()
72    }
73    #[must_use]
74    pub const fn session_output(&self) -> Option<&crate::AdapterOutput> {
75        self.session_output.as_ref()
76    }
77}
78
79/// The snapshot whose session cookies completed successfully in this dispatch.
80/// Response hooks may observe it; it never establishes authentication.
81#[must_use]
82pub fn published_session(
83    request: &impl SessionRequest,
84) -> Option<(crate::UserView, crate::SessionView)> {
85    published_session_snapshot(request).map(|snapshot| (snapshot.user, snapshot.session))
86}
87
88/// Observe immutable retained output without reconstructing a storage model.
89#[must_use]
90pub fn published_session_snapshot(
91    request: &impl SessionRequest,
92) -> Option<PublishedSessionSnapshot> {
93    request
94        .extensions()
95        .get::<PublishedSession>()
96        .and_then(|session| session.0.clone())
97}
98
99/// Retire cookie publication when a real login becomes a pending factor challenge.
100pub fn discard_issuance(request: &impl SessionRequest) {
101    request.extensions().insert(PublishedSession(None));
102    if let Some(pending) = request.extensions().get::<PendingIssuance>() {
103        *pending
104            .0
105            .lock()
106            .unwrap_or_else(std::sync::PoisonError::into_inner) = PendingData::default();
107    }
108}
109
110fn record_publication(snapshot: PublishedSessionSnapshot) {
111    if let Some(endpoint) = crate::endpoint::current_endpoint_call_context() {
112        endpoint
113            .extensions()
114            .insert(PublishedSession(Some(snapshot)));
115    } else if let Some(request) = crate::hooks::current_request_hook_context() {
116        request.extensions.insert(PublishedSession(Some(snapshot)));
117    }
118}
119
120/// Read the metadata attached to the actual authenticated hook snapshot.
121#[must_use]
122pub fn session_hook_cache_metadata(
123    request: &impl SessionRequest,
124) -> Option<SessionHookCacheMetadata> {
125    request
126        .extensions()
127        .get::<SessionHookCache>()
128        .and_then(|context| context.0.clone())
129}
130
131#[derive(Debug, Default)]
132struct PendingIssuance(Mutex<PendingData>);
133
134#[derive(Debug, Default)]
135struct PendingData {
136    prior_headers: Vec<String>,
137    cache_headers: Vec<String>,
138    ordinary_error: bool,
139}
140
141/// Result of a cache-aware HTTP session read. A cached user is an output
142/// snapshot, while Stored retains the actual application model.
143pub struct AuthenticatedRead<S: AuthSchema> {
144    pub user: crate::AuthenticatedUser<S>,
145    pub session: crate::SessionView,
146    pub needs_refresh: Option<bool>,
147}
148
149struct EstablishedSession<S: AuthSchema>(Option<EstablishedRead<S>>);
150
151struct EstablishedRead<S: AuthSchema> {
152    read: AuthenticatedRead<S>,
153    headers: std::collections::HashMap<String, String>,
154    virtual_session: Option<crate::SessionView>,
155    config: Arc<crate::AuthConfig>,
156    database: Arc<dyn crate::AuthStore<S>>,
157}
158
159/// Retire the ordinary middleware result before a sensitive physical read.
160pub fn clear_established_session<S: AuthSchema>(request: &impl SessionRequest) {
161    request.extensions().insert(EstablishedSession::<S>(None));
162}
163
164fn establish<S: AuthSchema>(
165    ctx: &AuthContext<S>,
166    request: &impl SessionRequest,
167    read: AuthenticatedRead<S>,
168) -> AuthenticatedRead<S> {
169    request
170        .extensions()
171        .insert(EstablishedSession::<S>(Some(EstablishedRead {
172            read: AuthenticatedRead {
173                user: read.user.clone(),
174                session: read.session.clone(),
175                needs_refresh: read.needs_refresh,
176            },
177            headers: request.session_headers().clone(),
178            virtual_session: request.virtual_session(ctx),
179            config: Arc::clone(&ctx.config),
180            database: Arc::clone(&ctx.database),
181        })));
182    read
183}
184
185impl<S: AuthSchema> std::fmt::Debug for AuthenticatedRead<S> {
186    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
187        f.debug_struct("AuthenticatedRead").finish_non_exhaustive()
188    }
189}
190
191/// Preserve a validated endpoint preference without trusting embedding state.
192/// Public dispatch resets request extensions before any endpoint executes.
193#[doc(hidden)]
194pub fn set_issuance_preference(request: &AuthRequest, dont_remember: bool) {
195    request
196        .extensions()
197        .insert(IssuancePreference(dont_remember));
198}