use crate::AdapterRecord;
use crate::field_policy::FieldValues;
use crate::types::AddTeamMemberResult;
use crate::user_validation::{PreparedUserCreation, UserValidationSource};
use crate::verification::{VerificationCreation, VerificationPublication, VerificationSnapshot};
use crate::{
AuthError, AuthResult, AuthSchema, AuthStore, CreateAccount, CreateJwk, CreateMember,
CreatePasskey, CreateSession, CreateUser, CreateVerification, Jwk, Member, Passkey, Team,
};
use async_trait::async_trait;
use std::any::Any;
use std::pin::Pin;
pub type BoxedTransactionValue = Box<dyn Any + Send>;
pub type TransactionFuture<'a> =
Pin<Box<dyn Future<Output = AuthResult<BoxedTransactionValue>> + Send + 'a>>;
pub type TypedTransactionFuture<'a, T> = Pin<Box<dyn Future<Output = AuthResult<T>> + Send + 'a>>;
pub type TransactionWork<S> =
dyn for<'tx> FnOnce(&'tx dyn AuthTransaction<S>) -> TransactionFuture<'tx> + Send;
#[async_trait]
pub trait AuthTransaction<S: AuthSchema>: Send + Sync {
async fn provider_verification_output(
&self,
_id: &str,
) -> AuthResult<Option<serde_json::Value>> {
Ok(None)
}
async fn prepare_secondary_session_creation(
&self,
_input: CreateSession,
_persist: bool,
) -> AuthResult<S::Session> {
Err(AuthError::NotImplemented(
"Transactional secondary session creation is unsupported".into(),
))
}
async fn prepare_secondary_session_update(
&self,
_session: S::Session,
_expires_at: Option<chrono::DateTime<chrono::Utc>>,
_fields: FieldValues,
) -> AuthResult<Option<(S::Session, FieldValues)>> {
Err(AuthError::NotImplemented(
"Transactional secondary session updates are unsupported".into(),
))
}
async fn complete_secondary_session_update(
&self,
_session: S::Session,
_expires_at: Option<chrono::DateTime<chrono::Utc>>,
_fields: FieldValues,
_persist: bool,
) -> AuthResult<Option<S::Session>> {
Err(AuthError::NotImplemented(
"Transactional secondary session updates are unsupported".into(),
))
}
async fn list_jwks(&self) -> AuthResult<Vec<Jwk>> {
Err(AuthError::config(
"Transactional JWKS reads are unsupported by this store",
))
}
async fn get_jwk_by_id(&self, _id: &str) -> AuthResult<Option<Jwk>> {
Err(AuthError::config(
"Transactional JWKS reads are unsupported by this store",
))
}
async fn create_jwk(&self, _data: CreateJwk) -> AuthResult<Jwk> {
Err(AuthError::config(
"Transactional JWKS writes are unsupported by this store",
))
}
async fn create_user_record(
&self,
create_user: CreateUser,
) -> AuthResult<AdapterRecord<S::User>> {
AdapterRecord::physical(self.create_user(create_user).await?)
}
async fn create_user_with_source_record(
&self,
create_user: CreateUser,
source: UserValidationSource,
) -> AuthResult<AdapterRecord<S::User>> {
AdapterRecord::physical(self.create_user_with_source(create_user, source).await?)
}
async fn create_user_prepared_record(
&self,
prepared: PreparedUserCreation,
) -> AuthResult<AdapterRecord<S::User>> {
AdapterRecord::physical(self.create_user_prepared(prepared).await?)
}
async fn get_user_by_id_record(&self, id: &str) -> AuthResult<Option<AdapterRecord<S::User>>> {
self.get_user_by_id(id)
.await?
.map(AdapterRecord::physical)
.transpose()
}
async fn create_account_record(
&self,
create_account: CreateAccount,
) -> AuthResult<AdapterRecord<S::Account>> {
AdapterRecord::physical(self.create_account(create_account).await?)
}
async fn create_session_record(
&self,
create_session: CreateSession,
) -> AuthResult<AdapterRecord<S::Session>> {
AdapterRecord::physical(self.create_session(create_session).await?)
}
async fn update_session_active_organization_record(
&self,
token: &str,
organization_id: Option<&str>,
) -> AuthResult<AdapterRecord<S::Session>> {
AdapterRecord::physical(
self.update_session_active_organization(token, organization_id)
.await?,
)
}
async fn update_session_active_team_record(
&self,
token: &str,
team_id: Option<&str>,
) -> AuthResult<AdapterRecord<S::Session>> {
AdapterRecord::physical(self.update_session_active_team(token, team_id).await?)
}
async fn create_verification_record(
&self,
_data: VerificationCreation,
_publication: VerificationPublication,
) -> AuthResult<Option<VerificationSnapshot>> {
Err(AuthError::NotImplemented(
"Verification publication phases are not supported by this store".into(),
))
}
async fn get_team(&self, _organization_id: &str, _team_id: &str) -> AuthResult<Option<Team>> {
Err(AuthError::NotImplemented(
"Team lookup in a transaction is not supported by this store".into(),
))
}
async fn add_team_member(
&self,
_team_id: &str,
_user_id: &str,
_maximum: Option<f64>,
) -> AuthResult<AddTeamMemberResult> {
Err(AuthError::NotImplemented(
"Team admission in a transaction is not supported by this store".into(),
))
}
async fn create_member(&self, _member: CreateMember) -> AuthResult<Member> {
Err(AuthError::NotImplemented(
"Member creation in a transaction is not supported by this store".into(),
))
}
async fn update_session_active_team(
&self,
_token: &str,
_team_id: Option<&str>,
) -> AuthResult<S::Session> {
Err(AuthError::NotImplemented(
"Active team updates in a transaction are not supported by this store".into(),
))
}
async fn update_session_active_organization(
&self,
_token: &str,
_organization_id: Option<&str>,
) -> AuthResult<S::Session> {
Err(AuthError::NotImplemented(
"Active organization updates in a transaction are not supported by this store".into(),
))
}
async fn get_user_by_id(&self, _id: &str) -> AuthResult<Option<S::User>> {
Err(AuthError::NotImplemented(
"User lookup in a transaction is not supported by this store".to_owned(),
))
}
async fn create_passkey(&self, _passkey: CreatePasskey) -> AuthResult<Passkey> {
Err(AuthError::NotImplemented(
"Passkey creation in a transaction is not supported by this store".to_owned(),
))
}
async fn create_user(&self, create_user: CreateUser) -> AuthResult<S::User>;
async fn create_user_with_source(
&self,
create_user: CreateUser,
_source: UserValidationSource,
) -> AuthResult<S::User> {
self.create_user(create_user).await
}
async fn create_user_prepared(&self, _prepared: PreparedUserCreation) -> AuthResult<S::User> {
Err(AuthError::NotImplemented(
"Prepared user creation is not supported by this transaction".into(),
))
}
async fn create_account(&self, create_account: CreateAccount) -> AuthResult<S::Account>;
async fn create_session(&self, create_session: CreateSession) -> AuthResult<S::Session>;
async fn create_verification(
&self,
_verification: CreateVerification,
) -> AuthResult<S::Verification> {
Err(AuthError::NotImplemented(
"Verification creation in a transaction is not supported by this store".to_owned(),
))
}
}
#[async_trait]
pub trait TransactionStore<S: AuthSchema>: Send + Sync {
async fn transaction_boxed(
&self,
work: Box<TransactionWork<S>>,
) -> AuthResult<BoxedTransactionValue>;
}
pub async fn transaction<S, T, F>(store: &dyn AuthStore<S>, work: F) -> AuthResult<T>
where
S: AuthSchema,
T: Send + 'static,
F: for<'tx> FnOnce(&'tx dyn AuthTransaction<S>) -> TypedTransactionFuture<'tx, T>
+ Send
+ 'static,
{
let value = store
.transaction_boxed(Box::new(move |tx| {
Box::pin(async move {
let result: BoxedTransactionValue = Box::new(work(tx).await?);
Ok(result)
})
}))
.await?;
value
.downcast::<T>()
.map(|boxed| *boxed)
.map_err(|_| AuthError::internal("store returned an invalid transaction payload"))
}