Skip to main content

alibi_core/store/
database_hooks.rs

1//! Database lifecycle hooks shared by the bundled persistence adapters.
2//!
3//! Each adapter names its connection and transaction handles through a
4//! [`HookBackend`]; the hook contract and its ordering are the same for all.
5
6use crate::config::AuthConfig;
7use crate::error::AuthResult;
8use crate::hooks::RequestHookContext;
9use crate::schema::AuthSchema;
10use crate::types::{
11    CreateAccount, CreateSession, CreateUser, CreateVerification, UpdateAccount, UpdateUser,
12    UpdateVerification,
13};
14use async_trait::async_trait;
15
16/// Control flow returned by database `before_*` hooks.
17#[derive(Debug, Clone, Copy, PartialEq, Eq)]
18pub enum HookControl {
19    Continue,
20    Cancel,
21}
22
23impl HookControl {
24    #[must_use]
25    pub const fn is_cancelled(self) -> bool {
26        matches!(self, Self::Cancel)
27    }
28}
29
30/// Database handles an adapter exposes to its lifecycle hooks.
31pub trait HookBackend: Send + Sync + 'static {
32    /// The adapter's shared connection or pool.
33    type Connection: Send + Sync;
34    /// The adapter's active auth transaction.
35    type Transaction: Send + Sync;
36}
37
38/// Context passed to database lifecycle hooks.
39pub struct DatabaseHookContext<'a, B: HookBackend> {
40    pub config: &'a AuthConfig,
41    pub db: &'a B::Connection,
42    /// Present for writes inside an auth transaction. Creation after hooks
43    /// run only after a successful commit and receive no active transaction.
44    pub tx: Option<&'a B::Transaction>,
45    pub request: Option<RequestHookContext>,
46}
47
48impl<B: HookBackend> std::fmt::Debug for DatabaseHookContext<'_, B> {
49    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
50        f.debug_struct("DatabaseHookContext")
51            .finish_non_exhaustive()
52    }
53}
54
55/// Lifecycle hooks for intercepting auth writes.
56///
57/// Transactional creation after callbacks retain the created snapshots and
58/// run in operation order after commit. Rollback discards them; callback errors
59/// are returned to the caller after the auth writes have committed.
60#[async_trait]
61pub trait DatabaseHooks<S: AuthSchema, B: HookBackend>: Send + Sync {
62    async fn before_create_user(
63        &self,
64        _user: &mut CreateUser,
65        _ctx: &DatabaseHookContext<'_, B>,
66    ) -> AuthResult<HookControl> {
67        Ok(HookControl::Continue)
68    }
69
70    async fn after_create_user(
71        &self,
72        _user: &S::User,
73        _ctx: &DatabaseHookContext<'_, B>,
74    ) -> AuthResult<()> {
75        Ok(())
76    }
77
78    async fn before_update_user(
79        &self,
80        _id: &str,
81        _update: &mut UpdateUser,
82        _ctx: &DatabaseHookContext<'_, B>,
83    ) -> AuthResult<HookControl> {
84        Ok(HookControl::Continue)
85    }
86
87    async fn after_update_user(
88        &self,
89        _user: &S::User,
90        _ctx: &DatabaseHookContext<'_, B>,
91    ) -> AuthResult<()> {
92        Ok(())
93    }
94
95    async fn before_delete_user(
96        &self,
97        _user: &S::User,
98        _ctx: &DatabaseHookContext<'_, B>,
99    ) -> AuthResult<HookControl> {
100        Ok(HookControl::Continue)
101    }
102
103    async fn after_delete_user(
104        &self,
105        _user: &S::User,
106        _ctx: &DatabaseHookContext<'_, B>,
107    ) -> AuthResult<()> {
108        Ok(())
109    }
110
111    async fn before_create_session(
112        &self,
113        _session: &mut CreateSession,
114        _ctx: &DatabaseHookContext<'_, B>,
115    ) -> AuthResult<HookControl> {
116        Ok(HookControl::Continue)
117    }
118
119    async fn after_create_session(
120        &self,
121        _session: &S::Session,
122        _ctx: &DatabaseHookContext<'_, B>,
123    ) -> AuthResult<()> {
124        Ok(())
125    }
126
127    async fn before_update_session(
128        &self,
129        _token: &str,
130        _fields: &mut crate::field_policy::FieldValues,
131        _ctx: &DatabaseHookContext<'_, B>,
132    ) -> AuthResult<HookControl> {
133        Ok(HookControl::Continue)
134    }
135
136    /// Observe an update that ran its before hooks but found no persisted row.
137    /// A before-hook veto does not invoke this callback.
138    async fn after_update_session_missing(
139        &self,
140        _token: &str,
141        _ctx: &DatabaseHookContext<'_, B>,
142    ) -> AuthResult<()> {
143        Ok(())
144    }
145
146    async fn after_update_session(
147        &self,
148        _session: &S::Session,
149        _ctx: &DatabaseHookContext<'_, B>,
150    ) -> AuthResult<()> {
151        Ok(())
152    }
153
154    async fn before_delete_session(
155        &self,
156        _session: &S::Session,
157        _ctx: &DatabaseHookContext<'_, B>,
158    ) -> AuthResult<HookControl> {
159        Ok(HookControl::Continue)
160    }
161
162    async fn after_delete_session(
163        &self,
164        _session: &S::Session,
165        _ctx: &DatabaseHookContext<'_, B>,
166    ) -> AuthResult<()> {
167        Ok(())
168    }
169
170    async fn before_create_account(
171        &self,
172        _account: &mut CreateAccount,
173        _ctx: &DatabaseHookContext<'_, B>,
174    ) -> AuthResult<HookControl> {
175        Ok(HookControl::Continue)
176    }
177
178    async fn after_create_account(
179        &self,
180        _account: &S::Account,
181        _ctx: &DatabaseHookContext<'_, B>,
182    ) -> AuthResult<()> {
183        Ok(())
184    }
185
186    async fn before_update_account(
187        &self,
188        _id: &str,
189        _update: &mut UpdateAccount,
190        _ctx: &DatabaseHookContext<'_, B>,
191    ) -> AuthResult<HookControl> {
192        Ok(HookControl::Continue)
193    }
194
195    async fn after_update_account(
196        &self,
197        _account: &S::Account,
198        _ctx: &DatabaseHookContext<'_, B>,
199    ) -> AuthResult<()> {
200        Ok(())
201    }
202
203    async fn before_delete_account(
204        &self,
205        _account: &S::Account,
206        _ctx: &DatabaseHookContext<'_, B>,
207    ) -> AuthResult<HookControl> {
208        Ok(HookControl::Continue)
209    }
210
211    async fn after_delete_account(
212        &self,
213        _account: &S::Account,
214        _ctx: &DatabaseHookContext<'_, B>,
215    ) -> AuthResult<()> {
216        Ok(())
217    }
218
219    async fn before_create_verification(
220        &self,
221        _verification: &mut CreateVerification,
222        _ctx: &DatabaseHookContext<'_, B>,
223    ) -> AuthResult<HookControl> {
224        Ok(HookControl::Continue)
225    }
226
227    /// Observe initialized defaults and transform the actual admitted candidate.
228    /// The legacy domain hook remains available for existing applications.
229    async fn before_create_verification_record(
230        &self,
231        candidate: &mut crate::verification::VerificationCreation,
232        ctx: &DatabaseHookContext<'_, B>,
233    ) -> AuthResult<HookControl> {
234        let mut data = candidate.data();
235        let control = self.before_create_verification(&mut data, ctx).await?;
236        candidate.identifier = data.identifier;
237        candidate.value = data.value;
238        candidate.expires_at = data.expires_at;
239        Ok(control)
240    }
241
242    /// Secondary-only values have no invented model or ID. The legacy model
243    /// callback is invoked only when a real physical model was persisted.
244    async fn after_create_verification_record(
245        &self,
246        snapshot: &crate::verification::VerificationSnapshot,
247        ctx: &DatabaseHookContext<'_, B>,
248    ) -> AuthResult<()> {
249        if let Some(model) = snapshot.original_model::<S::Verification>() {
250            self.after_create_verification(model, ctx).await?;
251        }
252        Ok(())
253    }
254
255    async fn after_create_verification(
256        &self,
257        _verification: &S::Verification,
258        _ctx: &DatabaseHookContext<'_, B>,
259    ) -> AuthResult<()> {
260        Ok(())
261    }
262
263    async fn before_delete_verification(
264        &self,
265        _verification: &S::Verification,
266        _ctx: &DatabaseHookContext<'_, B>,
267    ) -> AuthResult<HookControl> {
268        Ok(HookControl::Continue)
269    }
270
271    async fn before_update_verification(
272        &self,
273        _id: &str,
274        _update: &mut UpdateVerification,
275        _ctx: &DatabaseHookContext<'_, B>,
276    ) -> AuthResult<HookControl> {
277        Ok(HookControl::Continue)
278    }
279
280    async fn after_update_verification(
281        &self,
282        _verification: &S::Verification,
283        _ctx: &DatabaseHookContext<'_, B>,
284    ) -> AuthResult<()> {
285        Ok(())
286    }
287
288    /// Identifier updates invoke after hooks even when the adapter matched no
289    /// row. Actual physical snapshots bridge the existing model callback.
290    async fn after_update_verification_record(
291        &self,
292        snapshot: Option<&crate::verification::VerificationSnapshot>,
293        ctx: &DatabaseHookContext<'_, B>,
294    ) -> AuthResult<()> {
295        if let Some(model) = snapshot.and_then(|value| value.original_model::<S::Verification>()) {
296            self.after_update_verification(model, ctx).await?;
297        }
298        Ok(())
299    }
300
301    async fn after_delete_verification(
302        &self,
303        _verification: &S::Verification,
304        _ctx: &DatabaseHookContext<'_, B>,
305    ) -> AuthResult<()> {
306        Ok(())
307    }
308}