Skip to main content

alibi_core/store/contracts/
session.rs

1use super::*;
2#[async_trait]
3pub trait SessionStore<S: AuthSchema>: Send + Sync {
4    /// Stage a secondary session through creation hooks and optional physical persistence.
5    /// The caller publishes the cache before completing its after hooks.
6    async fn prepare_secondary_session_creation(
7        &self,
8        _input: CreateSession,
9        _persist: bool,
10    ) -> AuthResult<S::Session> {
11        Err(AuthError::NotImplemented(
12            "Secondary session creation is unsupported".into(),
13        ))
14    }
15    /// Complete creation hooks after the actual secondary writes succeed.
16    async fn complete_secondary_session_creation(&self, _session: &S::Session) -> AuthResult<()> {
17        Err(AuthError::NotImplemented(
18            "Secondary session creation is unsupported".into(),
19        ))
20    }
21    /// Bind a trusted secondary update once, retaining hook-mutated fields for physical publication.
22    async fn prepare_secondary_session_update(
23        &self,
24        _session: S::Session,
25        _expires_at: Option<chrono::DateTime<chrono::Utc>>,
26        _fields: crate::field_policy::FieldValues,
27    ) -> AuthResult<Option<(S::Session, crate::field_policy::FieldValues)>> {
28        Err(AuthError::NotImplemented(
29            "Secondary session updates are unsupported".into(),
30        ))
31    }
32    /// Publish the staged physical update and run after hooks exactly once.
33    async fn complete_secondary_session_update(
34        &self,
35        _session: S::Session,
36        _expires_at: Option<chrono::DateTime<chrono::Utc>>,
37        _fields: crate::field_policy::FieldValues,
38        _persist: bool,
39    ) -> AuthResult<Option<S::Session>> {
40        Err(AuthError::NotImplemented(
41            "Secondary session updates are unsupported".into(),
42        ))
43    }
44    /// End a still-live physical session without deleting its audit row.
45    async fn end_session_preserving(&self, _token: &str) -> AuthResult<()> {
46        Err(AuthError::NotImplemented(
47            "Preserving ended session rows is unsupported".into(),
48        ))
49    }
50    /// End all live session rows for one owner while retaining audit history.
51    async fn end_user_sessions_preserving(&self, _user_id: &str) -> AuthResult<()> {
52        Err(AuthError::NotImplemented(
53            "Preserving ended session rows is unsupported".into(),
54        ))
55    }
56    /// A typed user snapshot belonging to an authenticated session. The default
57    /// signals that the caller must use its physical user store.
58    async fn get_session_user(&self, _token: &str) -> AuthResult<Option<S::User>> {
59        Ok(None)
60    }
61
62    /// Retain declared adapter output for a secondary-backed session owner.
63    async fn get_session_user_record(
64        &self,
65        token: &str,
66    ) -> AuthResult<Option<crate::AdapterRecord<S::User>>> {
67        self.get_session_user(token)
68            .await?
69            .map(crate::AdapterRecord::physical)
70            .transpose()
71    }
72
73    /// Return a retained adapter record. The default is the physical model's
74    /// serialized snapshot; initialized stores apply their declared output policy.
75    async fn create_session_record(
76        &self,
77        create_session: CreateSession,
78    ) -> AuthResult<crate::AdapterRecord<S::Session>> {
79        crate::AdapterRecord::physical(self.create_session(create_session).await?)
80    }
81
82    /// Return a retained adapter record. The default is the physical model's
83    /// serialized snapshot; initialized stores apply their declared output policy.
84    async fn get_session_record(
85        &self,
86        token: &str,
87    ) -> AuthResult<Option<crate::AdapterRecord<S::Session>>> {
88        self.get_session(token)
89            .await?
90            .map(crate::AdapterRecord::physical)
91            .transpose()
92    }
93
94    /// Return a retained adapter record. The default is the physical model's
95    /// serialized snapshot; initialized stores apply their declared output policy.
96    async fn get_sessions_by_tokens_record(
97        &self,
98        tokens: &[String],
99    ) -> AuthResult<Vec<crate::AdapterRecord<S::Session>>> {
100        self.get_sessions_by_tokens(tokens)
101            .await?
102            .into_iter()
103            .map(crate::AdapterRecord::physical)
104            .collect()
105    }
106
107    /// Return a retained adapter record. The default is the physical model's
108    /// serialized snapshot; initialized stores apply their declared output policy.
109    async fn get_user_sessions_record(
110        &self,
111        user_id: &str,
112    ) -> AuthResult<Vec<crate::AdapterRecord<S::Session>>> {
113        self.get_user_sessions(user_id)
114            .await?
115            .into_iter()
116            .map(crate::AdapterRecord::physical)
117            .collect()
118    }
119
120    /// Return active physical sessions after declared output transforms. Expiry
121    /// selection runs before application callbacks, without trusting output fields.
122    async fn get_active_user_sessions_record(
123        &self,
124        user_id: &str,
125    ) -> AuthResult<Vec<crate::AdapterRecord<S::Session>>> {
126        use crate::AuthSession;
127        let now = chrono::Utc::now();
128        self.get_user_sessions(user_id)
129            .await?
130            .into_iter()
131            .filter(|session| session.expires_at() > now && session.active())
132            .map(crate::AdapterRecord::physical)
133            .collect()
134    }
135
136    /// Return a retained adapter record. The default is the physical model's
137    /// serialized snapshot; initialized stores apply their declared output policy.
138    async fn refresh_session_record(
139        &self,
140        token: &str,
141        expires_at: chrono::DateTime<chrono::Utc>,
142    ) -> AuthResult<Option<crate::AdapterRecord<S::Session>>> {
143        self.refresh_session(token, expires_at)
144            .await?
145            .map(crate::AdapterRecord::physical)
146            .transpose()
147    }
148
149    /// Return a retained adapter record. The default is the physical model's
150    /// serialized snapshot; initialized stores apply their declared output policy.
151    async fn update_session_fields_record(
152        &self,
153        token: &str,
154        fields: crate::field_policy::FieldValues,
155    ) -> AuthResult<Option<crate::AdapterRecord<S::Session>>> {
156        self.update_session_fields(token, fields)
157            .await?
158            .map(crate::AdapterRecord::physical)
159            .transpose()
160    }
161
162    /// Return a retained adapter record. The default is the physical model's
163    /// serialized snapshot; initialized stores apply their declared output policy.
164    async fn update_session_active_organization_record(
165        &self,
166        token: &str,
167        organization_id: Option<&str>,
168    ) -> AuthResult<crate::AdapterRecord<S::Session>> {
169        crate::AdapterRecord::physical(
170            self.update_session_active_organization(token, organization_id)
171                .await?,
172        )
173    }
174
175    /// Return a retained adapter record. The default is the physical model's
176    /// serialized snapshot; initialized stores apply their declared output policy.
177    async fn update_session_active_team_record(
178        &self,
179        token: &str,
180        team_id: Option<&str>,
181    ) -> AuthResult<crate::AdapterRecord<S::Session>> {
182        crate::AdapterRecord::physical(self.update_session_active_team(token, team_id).await?)
183    }
184
185    /// Persist already authorized fields for the currently authenticated token.
186    async fn update_session_fields(
187        &self,
188        _token: &str,
189        _fields: crate::field_policy::FieldValues,
190    ) -> AuthResult<Option<S::Session>> {
191        Err(AuthError::internal(
192            "the store does not support session field updates",
193        ))
194    }
195
196    async fn create_session(&self, create_session: CreateSession) -> AuthResult<S::Session>;
197
198    async fn get_session(&self, token: &str) -> AuthResult<Option<S::Session>>;
199
200    /// Fetch matching sessions once each, including expired rows. The bundled
201    /// SQLite adapter returns token-index order rather than request order.
202    async fn get_sessions_by_tokens(&self, tokens: &[String]) -> AuthResult<Vec<S::Session>> {
203        let mut sessions = Vec::new();
204        for token in BTreeSet::from_iter(tokens) {
205            if let Some(session) = self.get_session(token).await? {
206                sessions.push(session);
207            }
208        }
209        Ok(sessions)
210    }
211
212    async fn get_user_sessions(&self, user_id: &str) -> AuthResult<Vec<S::Session>>;
213
214    async fn update_session_expiry(
215        &self,
216        token: &str,
217        expires_at: chrono::DateTime<chrono::Utc>,
218    ) -> AuthResult<()>;
219
220    /// Refresh expiry and configured fields in one physical update. Adapters
221    /// supporting additional values must override this operation so their actual
222    /// before hooks precede binding and their after hooks see the final row.
223    /// The fallback preserves plain refresh and fails closed on additional writes.
224    async fn refresh_session_with_fields(
225        &self,
226        token: &str,
227        expires_at: chrono::DateTime<chrono::Utc>,
228        mut fields: crate::field_policy::FieldValues,
229    ) -> AuthResult<Option<S::Session>> {
230        fields.apply_adapter_transforms_async().await?;
231        if !fields.is_empty() {
232            return Err(AuthError::NotImplemented(
233                "The store does not support refresh field updates".into(),
234            ));
235        }
236        self.refresh_session(token, expires_at).await
237    }
238
239    /// Refresh the persisted expiry and return the updated snapshot. A session
240    /// removed before the update returns `None`, never its old credentials.
241    async fn refresh_session(
242        &self,
243        token: &str,
244        expires_at: chrono::DateTime<chrono::Utc>,
245    ) -> AuthResult<Option<S::Session>> {
246        match self.update_session_expiry(token, expires_at).await {
247            Ok(()) => self.get_session(token).await,
248            Err(AuthError::SessionNotFound) => Ok(None),
249            Err(error) => Err(error),
250        }
251    }
252
253    async fn delete_session(&self, token: &str) -> AuthResult<()>;
254
255    async fn delete_user_sessions(&self, user_id: &str) -> AuthResult<()>;
256
257    async fn delete_expired_sessions(&self) -> AuthResult<usize>;
258
259    async fn update_session_active_organization(
260        &self,
261        token: &str,
262        organization_id: Option<&str>,
263    ) -> AuthResult<S::Session>;
264
265    async fn update_session_active_team(
266        &self,
267        _token: &str,
268        _team_id: Option<&str>,
269    ) -> AuthResult<S::Session> {
270        Err(AuthError::internal(
271            "active-team updates are not supported by this store",
272        ))
273    }
274}