Skip to main content

alibi_core/config/
session.rs

1use super::SameSite;
2use crate::utils::javascript::number_from_i64;
3use chrono::Duration;
4use std::sync::Arc;
5/// Session-specific configuration
6#[derive(Clone)]
7pub struct SessionConfig {
8    /// No durable server session authority. Session records are instance-local
9    /// in memory; no SQL session rows are read or written. Captured caches remain
10    /// replayable until their embedded expiry or cache version/secret invalidation.
11    pub stateless: bool,
12    /// Stateless envelope renewal. Stateful deployments ignore this policy.
13    pub cookie_refresh_cache: CookieRefreshCache,
14    /// Shared secondary session backend for stateful deployments.
15    pub secondary_storage: Option<Arc<dyn crate::store::CacheAdapter>>,
16    /// Also persist session rows when a secondary backend is configured.
17    pub store_in_database: bool,
18    /// Retain ended database sessions for auditing; cache misses never fall back to them.
19    pub preserve_in_database: bool,
20    /// Additional fields accepted by session input and output policies.
21    pub additional_fields: indexmap::IndexMap<String, crate::field_policy::FieldConfig>,
22    /// Session expiration duration
23    pub expires_in: Duration,
24
25    /// How often to refresh the session expiry (as a Duration).
26    ///
27    /// A read refreshes when `expires_at - expires_in + update_age` is due.
28    /// This uses the stored expiry, including application overrides, rather
29    /// than the row's last update timestamp. `None` refreshes on every read
30    /// unless refresh is disabled or deferred.
31    pub update_age: Option<Duration>,
32
33    /// If `true`, sessions are never automatically refreshed on access.
34    pub disable_session_refresh: bool,
35
36    /// Defer refresh and expired-row deletion during `GET` session reads.
37    /// `POST /get-session` performs these writes when enabled; otherwise that
38    /// method is rejected with 405. Expired browser cookies are still cleared.
39    pub defer_session_refresh: bool,
40
41    /// Session freshness window, defaulting to one day. `None` or zero skips
42    /// the freshness restriction; a positive window checks creation time.
43    pub fresh_age: Option<Duration>,
44
45    /// Cookie name for session token
46    pub cookie_name: String,
47
48    /// Cookie settings
49    pub cookie_secure: bool,
50    pub cookie_http_only: bool,
51    pub cookie_same_site: SameSite,
52
53    /// Optional cookie-based session cache to avoid DB lookups.
54    ///
55    /// When enabled, session data is cached in a signed/encrypted cookie.
56    /// `SessionManager` checks the cookie cache before hitting the database.
57    pub cookie_cache: Option<CookieCacheConfig>,
58}
59
60impl std::fmt::Debug for SessionConfig {
61    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
62        f.debug_struct("SessionConfig")
63            .field("secondary_storage", &self.secondary_storage.is_some())
64            .field("store_in_database", &self.store_in_database)
65            .field("preserve_in_database", &self.preserve_in_database)
66            .field("expires_in", &self.expires_in)
67            .finish_non_exhaustive()
68    }
69}
70
71/// JWT configuration
72#[derive(Debug, Clone)]
73pub struct JwtConfig {
74    /// JWT expiration duration
75    pub expires_in: Duration,
76
77    /// JWT algorithm
78    pub algorithm: String,
79
80    /// Issuer claim
81    pub issuer: Option<String>,
82
83    /// Audience claim
84    pub audience: Option<String>,
85}
86
87/// Configuration for cookie-based session caching.
88///
89/// When enabled, session data is stored in a signed or encrypted cookie so that
90/// subsequent requests can skip the database lookup.
91#[derive(Debug, Clone)]
92pub struct CookieCacheConfig {
93    /// Whether the cookie cache is active.
94    pub enabled: bool,
95
96    /// Maximum age in seconds before a fresh DB lookup is required.
97    ///
98    /// JavaScript falsy zero/NaN selects 300; other IEEE754 values are retained.
99    ///
100    /// Default: 5 minutes.
101    pub max_age: f64,
102
103    /// Strategy used to protect the cached cookie value.
104    ///
105    /// JWT uses the auth secret unless a locally managed JWT plugin signer is installed.
106    pub strategy: CookieCacheStrategy,
107
108    /// Literal or asynchronous application-owned version policy.
109    pub version: Option<crate::session::cookie_cache::CookieCacheVersion>,
110}
111
112/// Stateless cache renewal policy, corresponding to `cookieCache.refreshCache`.
113#[derive(Debug, Clone, Copy, Default)]
114pub enum CookieRefreshCache {
115    /// No cache renewal.
116    #[default]
117    Disabled,
118    /// Renew when remaining envelope lifetime is below floor(maxAge * 0.2).
119    Automatic,
120    /// Renew below this remaining lifetime in seconds (JavaScript Number).
121    UpdateAge(f64),
122}
123
124impl SessionConfig {
125    /// Select cookie-only sessions, installing the pinned no-store defaults.
126    /// Configure `cookie_cache` afterwards to override strategy, lifetime,
127    /// version, or renewal. Existing database defaults are unchanged.
128    #[must_use]
129    pub fn stateless(mut self) -> Self {
130        if !self.stateless {
131            self.cookie_refresh_cache = CookieRefreshCache::Automatic;
132        }
133        self.stateless = true;
134        if self.cookie_cache.is_none() {
135            self.cookie_cache = Some(CookieCacheConfig {
136                enabled: true,
137                strategy: CookieCacheStrategy::Jwe,
138                max_age: number_from_i64(self.expires_in.num_seconds()),
139                ..CookieCacheConfig::default()
140            });
141        }
142        self
143    }
144
145    /// Whether deployment has durable server session storage.
146    #[must_use]
147    pub fn has_server_session_store(&self) -> bool {
148        !self.stateless
149    }
150}
151
152/// Strategy for signing / encrypting the cookie cache.
153#[derive(Debug, Clone, PartialEq, Eq)]
154pub enum CookieCacheStrategy {
155    /// Base64url-encoded payload + HMAC-SHA256 signature.
156    Compact,
157    /// Standard JWT with HMAC signing.
158    Jwt,
159    /// JWE with direct-key AES-256-CBC/HMAC-SHA512 authenticated encryption.
160    Jwe,
161}
162
163impl Default for CookieCacheConfig {
164    fn default() -> Self {
165        Self {
166            enabled: false,
167            max_age: 300.0,
168            strategy: CookieCacheStrategy::Compact,
169            version: None,
170        }
171    }
172}
173
174impl Default for SessionConfig {
175    fn default() -> Self {
176        Self {
177            stateless: false,
178            cookie_refresh_cache: CookieRefreshCache::Disabled,
179            secondary_storage: None,
180            store_in_database: false,
181            preserve_in_database: false,
182            additional_fields: indexmap::IndexMap::default(),
183            expires_in: Duration::hours(24 * 7),   // 7 days
184            update_age: Some(Duration::hours(24)), // refresh once per day
185            disable_session_refresh: false,
186            defer_session_refresh: false,
187            fresh_age: Some(Duration::days(1)),
188            cookie_name: "better-auth.session_token".to_owned(),
189            // Secure flag is derived from base_url scheme (HTTPS → true).
190            // Default base_url is http://localhost:3000, so default is false.
191            cookie_secure: false,
192            cookie_http_only: true,
193            cookie_same_site: SameSite::Lax,
194            cookie_cache: None,
195        }
196    }
197}
198
199impl Default for JwtConfig {
200    fn default() -> Self {
201        Self {
202            expires_in: Duration::hours(24), // 1 day
203            algorithm: "HS256".to_owned(),
204            issuer: None,
205            audience: None,
206        }
207    }
208}