alibi_core/store/contracts/
transaction.rs1use crate::AdapterRecord;
2use crate::field_policy::FieldValues;
3use crate::types::AddTeamMemberResult;
4use crate::user_validation::{PreparedUserCreation, UserValidationSource};
5use crate::verification::{VerificationCreation, VerificationPublication, VerificationSnapshot};
6use crate::{
7 AuthError, AuthResult, AuthSchema, AuthStore, CreateAccount, CreateJwk, CreateMember,
8 CreatePasskey, CreateSession, CreateUser, CreateVerification, Jwk, Member, Passkey, Team,
9};
10use async_trait::async_trait;
11use std::any::Any;
12use std::pin::Pin;
13pub type BoxedTransactionValue = Box<dyn Any + Send>;
14
15pub type TransactionFuture<'a> =
16 Pin<Box<dyn Future<Output = AuthResult<BoxedTransactionValue>> + Send + 'a>>;
17
18pub type TypedTransactionFuture<'a, T> = Pin<Box<dyn Future<Output = AuthResult<T>> + Send + 'a>>;
19
20pub type TransactionWork<S> =
21 dyn for<'tx> FnOnce(&'tx dyn AuthTransaction<S>) -> TransactionFuture<'tx> + Send;
22
23#[async_trait]
24pub trait AuthTransaction<S: AuthSchema>: Send + Sync {
25 async fn provider_verification_output(
29 &self,
30 _id: &str,
31 ) -> AuthResult<Option<serde_json::Value>> {
32 Ok(None)
33 }
34
35 async fn prepare_secondary_session_creation(
37 &self,
38 _input: CreateSession,
39 _persist: bool,
40 ) -> AuthResult<S::Session> {
41 Err(AuthError::NotImplemented(
42 "Transactional secondary session creation is unsupported".into(),
43 ))
44 }
45
46 async fn prepare_secondary_session_update(
48 &self,
49 _session: S::Session,
50 _expires_at: Option<chrono::DateTime<chrono::Utc>>,
51 _fields: FieldValues,
52 ) -> AuthResult<Option<(S::Session, FieldValues)>> {
53 Err(AuthError::NotImplemented(
54 "Transactional secondary session updates are unsupported".into(),
55 ))
56 }
57 async fn complete_secondary_session_update(
59 &self,
60 _session: S::Session,
61 _expires_at: Option<chrono::DateTime<chrono::Utc>>,
62 _fields: FieldValues,
63 _persist: bool,
64 ) -> AuthResult<Option<S::Session>> {
65 Err(AuthError::NotImplemented(
66 "Transactional secondary session updates are unsupported".into(),
67 ))
68 }
69
70 async fn list_jwks(&self) -> AuthResult<Vec<Jwk>> {
72 Err(AuthError::config(
73 "Transactional JWKS reads are unsupported by this store",
74 ))
75 }
76 async fn get_jwk_by_id(&self, _id: &str) -> AuthResult<Option<Jwk>> {
78 Err(AuthError::config(
79 "Transactional JWKS reads are unsupported by this store",
80 ))
81 }
82 async fn create_jwk(&self, _data: CreateJwk) -> AuthResult<Jwk> {
84 Err(AuthError::config(
85 "Transactional JWKS writes are unsupported by this store",
86 ))
87 }
88
89 async fn create_user_record(
92 &self,
93 create_user: CreateUser,
94 ) -> AuthResult<AdapterRecord<S::User>> {
95 AdapterRecord::physical(self.create_user(create_user).await?)
96 }
97
98 async fn create_user_with_source_record(
101 &self,
102 create_user: CreateUser,
103 source: UserValidationSource,
104 ) -> AuthResult<AdapterRecord<S::User>> {
105 AdapterRecord::physical(self.create_user_with_source(create_user, source).await?)
106 }
107
108 async fn create_user_prepared_record(
111 &self,
112 prepared: PreparedUserCreation,
113 ) -> AuthResult<AdapterRecord<S::User>> {
114 AdapterRecord::physical(self.create_user_prepared(prepared).await?)
115 }
116
117 async fn get_user_by_id_record(&self, id: &str) -> AuthResult<Option<AdapterRecord<S::User>>> {
120 self.get_user_by_id(id)
121 .await?
122 .map(AdapterRecord::physical)
123 .transpose()
124 }
125
126 async fn create_account_record(
129 &self,
130 create_account: CreateAccount,
131 ) -> AuthResult<AdapterRecord<S::Account>> {
132 AdapterRecord::physical(self.create_account(create_account).await?)
133 }
134
135 async fn create_session_record(
138 &self,
139 create_session: CreateSession,
140 ) -> AuthResult<AdapterRecord<S::Session>> {
141 AdapterRecord::physical(self.create_session(create_session).await?)
142 }
143
144 async fn update_session_active_organization_record(
147 &self,
148 token: &str,
149 organization_id: Option<&str>,
150 ) -> AuthResult<AdapterRecord<S::Session>> {
151 AdapterRecord::physical(
152 self.update_session_active_organization(token, organization_id)
153 .await?,
154 )
155 }
156
157 async fn update_session_active_team_record(
160 &self,
161 token: &str,
162 team_id: Option<&str>,
163 ) -> AuthResult<AdapterRecord<S::Session>> {
164 AdapterRecord::physical(self.update_session_active_team(token, team_id).await?)
165 }
166
167 async fn create_verification_record(
170 &self,
171 _data: VerificationCreation,
172 _publication: VerificationPublication,
173 ) -> AuthResult<Option<VerificationSnapshot>> {
174 Err(AuthError::NotImplemented(
175 "Verification publication phases are not supported by this store".into(),
176 ))
177 }
178 async fn get_team(&self, _organization_id: &str, _team_id: &str) -> AuthResult<Option<Team>> {
180 Err(AuthError::NotImplemented(
181 "Team lookup in a transaction is not supported by this store".into(),
182 ))
183 }
184 async fn add_team_member(
186 &self,
187 _team_id: &str,
188 _user_id: &str,
189 _maximum: Option<f64>,
190 ) -> AuthResult<AddTeamMemberResult> {
191 Err(AuthError::NotImplemented(
192 "Team admission in a transaction is not supported by this store".into(),
193 ))
194 }
195 async fn create_member(&self, _member: CreateMember) -> AuthResult<Member> {
196 Err(AuthError::NotImplemented(
197 "Member creation in a transaction is not supported by this store".into(),
198 ))
199 }
200 async fn update_session_active_team(
202 &self,
203 _token: &str,
204 _team_id: Option<&str>,
205 ) -> AuthResult<S::Session> {
206 Err(AuthError::NotImplemented(
207 "Active team updates in a transaction are not supported by this store".into(),
208 ))
209 }
210 async fn update_session_active_organization(
211 &self,
212 _token: &str,
213 _organization_id: Option<&str>,
214 ) -> AuthResult<S::Session> {
215 Err(AuthError::NotImplemented(
216 "Active organization updates in a transaction are not supported by this store".into(),
217 ))
218 }
219 async fn get_user_by_id(&self, _id: &str) -> AuthResult<Option<S::User>> {
221 Err(AuthError::NotImplemented(
222 "User lookup in a transaction is not supported by this store".to_owned(),
223 ))
224 }
225 async fn create_passkey(&self, _passkey: CreatePasskey) -> AuthResult<Passkey> {
227 Err(AuthError::NotImplemented(
228 "Passkey creation in a transaction is not supported by this store".to_owned(),
229 ))
230 }
231 async fn create_user(&self, create_user: CreateUser) -> AuthResult<S::User>;
232 async fn create_user_with_source(
235 &self,
236 create_user: CreateUser,
237 _source: UserValidationSource,
238 ) -> AuthResult<S::User> {
239 self.create_user(create_user).await
240 }
241 async fn create_user_prepared(&self, _prepared: PreparedUserCreation) -> AuthResult<S::User> {
244 Err(AuthError::NotImplemented(
245 "Prepared user creation is not supported by this transaction".into(),
246 ))
247 }
248 async fn create_account(&self, create_account: CreateAccount) -> AuthResult<S::Account>;
249 async fn create_session(&self, create_session: CreateSession) -> AuthResult<S::Session>;
250 async fn create_verification(
251 &self,
252 _verification: CreateVerification,
253 ) -> AuthResult<S::Verification> {
254 Err(AuthError::NotImplemented(
255 "Verification creation in a transaction is not supported by this store".to_owned(),
256 ))
257 }
258}
259
260#[async_trait]
261pub trait TransactionStore<S: AuthSchema>: Send + Sync {
262 async fn transaction_boxed(
263 &self,
264 work: Box<TransactionWork<S>>,
265 ) -> AuthResult<BoxedTransactionValue>;
266}
267
268pub async fn transaction<S, T, F>(store: &dyn AuthStore<S>, work: F) -> AuthResult<T>
272where
273 S: AuthSchema,
274 T: Send + 'static,
275 F: for<'tx> FnOnce(&'tx dyn AuthTransaction<S>) -> TypedTransactionFuture<'tx, T>
276 + Send
277 + 'static,
278{
279 let value = store
280 .transaction_boxed(Box::new(move |tx| {
281 Box::pin(async move {
282 let result: BoxedTransactionValue = Box::new(work(tx).await?);
283 Ok(result)
284 })
285 }))
286 .await?;
287
288 value
289 .downcast::<T>()
290 .map(|boxed| *boxed)
291 .map_err(|_| AuthError::internal("store returned an invalid transaction payload"))
292}