Skip to main content

alibi_core/session/cookie_cache/runtime/
mod.rs

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