1mod memory;
9#[cfg(feature = "postgres")]
10#[cfg_attr(docsrs, doc(cfg(feature = "postgres")))]
11mod postgres;
12#[cfg(feature = "valkey")]
13#[cfg_attr(docsrs, doc(cfg(feature = "valkey")))]
14mod valkey;
15
16use std::future::Future;
17
18use chrono::{DateTime, TimeDelta, Utc};
19use uuid::Uuid;
20
21pub use memory::MemoryStore;
22#[cfg(feature = "postgres")]
23#[cfg_attr(docsrs, doc(cfg(feature = "postgres")))]
24pub use postgres::PgStore;
25#[cfg(feature = "valkey")]
26#[cfg_attr(docsrs, doc(cfg(feature = "valkey")))]
27pub use valkey::ValkeyStore;
28
29use crate::{
30 error::StoreError,
31 event::{EventKind, SessionEvent},
32 fingerprint::Baseline,
33 token::TokenHash,
34};
35
36#[derive(Clone, Debug, PartialEq, Eq)]
38pub struct SessionRecord {
39 pub id: Uuid,
41 pub subject: String,
43 pub created_at: DateTime<Utc>,
45 pub authenticated_at: DateTime<Utc>,
47 pub last_seen_at: DateTime<Utc>,
50 pub expires_at: DateTime<Utc>,
52 pub step_up_required: bool,
54 pub baseline: Baseline,
56}
57
58#[derive(Clone, Copy, Debug, PartialEq, Eq)]
60pub struct TokenRecord {
61 pub hash: TokenHash,
63 pub issued_at: DateTime<Utc>,
65 pub first_used_at: Option<DateTime<Utc>>,
67 pub superseded_at: Option<DateTime<Utc>>,
69}
70
71#[derive(Clone, Debug, PartialEq, Eq)]
73pub struct TokenLookup {
74 pub session: SessionRecord,
76 pub token: TokenRecord,
78 pub newer_token_used: bool,
81}
82
83#[derive(Clone, Copy, Debug, PartialEq, Eq)]
86pub enum Supersede {
87 Exact(TokenHash),
90 CurrentIssuedBefore(DateTime<Utc>),
92 Current,
94}
95
96#[derive(Clone, Copy, Debug, PartialEq, Eq)]
98pub struct Rotation {
99 pub new_token: TokenHash,
101 pub supersede: Supersede,
103 pub prune_superseded_before: DateTime<Utc>,
105}
106
107#[derive(Clone, Debug, PartialEq, Eq)]
109pub struct SessionUpdate {
110 pub session_id: Uuid,
112 pub now: DateTime<Utc>,
114 pub mark_used: Option<TokenHash>,
116 pub baseline: Option<Baseline>,
118 pub step_up_required: Option<bool>,
120 pub authenticated_at: Option<DateTime<Utc>>,
122 pub rotation: Option<Rotation>,
124}
125
126impl SessionUpdate {
127 pub fn touch(session_id: Uuid, now: DateTime<Utc>) -> Self {
129 Self {
130 session_id,
131 now,
132 mark_used: None,
133 baseline: None,
134 step_up_required: None,
135 authenticated_at: None,
136 rotation: None,
137 }
138 }
139}
140
141pub trait SessionStore: Send + Sync + 'static {
146 fn insert_session(
149 &self,
150 session: &SessionRecord,
151 token: &TokenHash,
152 ) -> impl Future<Output = Result<(), StoreError>> + Send;
153
154 fn find_by_token(&self, token: &TokenHash) -> impl Future<Output = Result<Option<TokenLookup>, StoreError>> + Send;
156
157 fn apply(&self, update: &SessionUpdate) -> impl Future<Output = Result<bool, StoreError>> + Send;
168
169 fn delete_session(&self, session_id: Uuid) -> impl Future<Output = Result<bool, StoreError>> + Send;
171
172 fn delete_subject_sessions(&self, subject: &str) -> impl Future<Output = Result<u64, StoreError>> + Send;
174
175 fn list_subject_sessions(
177 &self,
178 subject: &str,
179 ) -> impl Future<Output = Result<Vec<SessionRecord>, StoreError>> + Send;
180
181 fn delete_expired(
184 &self,
185 now: DateTime<Utc>,
186 idle_cutoff: DateTime<Utc>,
187 ) -> impl Future<Output = Result<u64, StoreError>> + Send;
188
189 fn record_event(&self, event: &SessionEvent) -> impl Future<Output = Result<(), StoreError>> + Send;
191
192 fn delete_events_before(&self, cutoff: DateTime<Utc>) -> impl Future<Output = Result<u64, StoreError>> + Send;
195
196 fn claim_event(
203 &self,
204 session_id: Uuid,
205 kind: EventKind,
206 now: DateTime<Utc>,
207 window: TimeDelta,
208 ) -> impl Future<Output = Result<bool, StoreError>> + Send {
209 let _ = (session_id, kind, now, window);
210 std::future::ready(Ok(true))
211 }
212}
213
214impl<T: SessionStore> SessionStore for std::sync::Arc<T> {
215 fn insert_session(
216 &self,
217 session: &SessionRecord,
218 token: &TokenHash,
219 ) -> impl Future<Output = Result<(), StoreError>> + Send {
220 (**self).insert_session(session, token)
221 }
222
223 fn find_by_token(&self, token: &TokenHash) -> impl Future<Output = Result<Option<TokenLookup>, StoreError>> + Send {
224 (**self).find_by_token(token)
225 }
226
227 fn apply(&self, update: &SessionUpdate) -> impl Future<Output = Result<bool, StoreError>> + Send {
228 (**self).apply(update)
229 }
230
231 fn delete_session(&self, session_id: Uuid) -> impl Future<Output = Result<bool, StoreError>> + Send {
232 (**self).delete_session(session_id)
233 }
234
235 fn delete_subject_sessions(&self, subject: &str) -> impl Future<Output = Result<u64, StoreError>> + Send {
236 (**self).delete_subject_sessions(subject)
237 }
238
239 fn list_subject_sessions(
240 &self,
241 subject: &str,
242 ) -> impl Future<Output = Result<Vec<SessionRecord>, StoreError>> + Send {
243 (**self).list_subject_sessions(subject)
244 }
245
246 fn delete_expired(
247 &self,
248 now: DateTime<Utc>,
249 idle_cutoff: DateTime<Utc>,
250 ) -> impl Future<Output = Result<u64, StoreError>> + Send {
251 (**self).delete_expired(now, idle_cutoff)
252 }
253
254 fn record_event(&self, event: &SessionEvent) -> impl Future<Output = Result<(), StoreError>> + Send {
255 (**self).record_event(event)
256 }
257
258 fn delete_events_before(&self, cutoff: DateTime<Utc>) -> impl Future<Output = Result<u64, StoreError>> + Send {
259 (**self).delete_events_before(cutoff)
260 }
261
262 fn claim_event(
263 &self,
264 session_id: Uuid,
265 kind: EventKind,
266 now: DateTime<Utc>,
267 window: TimeDelta,
268 ) -> impl Future<Output = Result<bool, StoreError>> + Send {
269 (**self).claim_event(session_id, kind, now, window)
270 }
271}