alibi-core 0.8.0

Shared authentication contracts and backend-independent services for Alibi
Documentation
use crate::store::VerificationStore;
use crate::store::stateless::{StatelessSchema, StatelessStore};
use crate::{AuthError, AuthResult, CreateVerification, VerificationView};
use async_trait::async_trait;
use chrono::{DateTime, Utc};
#[async_trait]
impl VerificationStore<StatelessSchema> for StatelessStore {
    async fn create_verification_record(
        &self,
        data: crate::verification::VerificationCreation,
        publication: crate::verification::VerificationPublication,
    ) -> AuthResult<Option<crate::verification::VerificationSnapshot>> {
        let snapshot = if publication.store_in_database {
            let model = VerificationView {
                id: data.id.unwrap_or_else(|| uuid::Uuid::new_v4().to_string()),
                identifier: data.identifier,
                value: data.value,
                expires_at: data.expires_at,
                created_at: data.created_at,
                updated_at: data.updated_at,
            };
            let mut state = self.lock()?;
            if state.verifications.contains_key(&model.id) {
                return Err(AuthError::internal("duplicate verification primary ID"));
            }
            _ = state.verifications.insert(model.id.clone(), model.clone());
            crate::verification::VerificationSnapshot::from_model(&model)
        } else {
            data.snapshot()
        };
        publication.publish(&snapshot).await?;
        Ok(Some(snapshot))
    }

    async fn consume_verification_snapshot(
        &self,
        identifier: &str,
    ) -> AuthResult<Option<VerificationView>> {
        let mut state = self.lock()?;
        let found = state
            .verifications
            .values()
            .filter(|verification| verification.identifier == identifier)
            .max_by_key(|verification| verification.created_at)
            .cloned();
        state
            .verifications
            .retain(|_, sibling| sibling.identifier != identifier);
        Ok(found)
    }

    async fn update_verification_by_identifier(
        &self,
        identifier: &str,
        data: crate::UpdateVerification,
    ) -> AuthResult<Option<crate::verification::VerificationSnapshot>> {
        let mut state = self.lock()?;
        let mut found = None;
        for model in state.verifications.values_mut() {
            if model.identifier == identifier {
                model.updated_at = Utc::now();
                if let Some(value) = &data.value {
                    model.value.clone_from(value);
                }
                if let Some(expiry) = data.expires_at {
                    model.expires_at = expiry;
                }
                if found.is_none() {
                    found = Some(crate::verification::VerificationSnapshot::from_model(model));
                }
            }
        }
        Ok(found)
    }

    async fn reserve_verification_record(
        &self,
        logical_identifier: &str,
        data: CreateVerification,
    ) -> AuthResult<Option<VerificationView>> {
        let (id, _) = crate::store::verification_reservation_key(logical_identifier);
        let mut state = self.lock()?;
        let indexmap::map::Entry::Vacant(entry) = state.verifications.entry(id.clone()) else {
            return Ok(None);
        };
        let now = Utc::now();
        let model = VerificationView {
            id,
            identifier: data.identifier,
            value: data.value,
            expires_at: data.expires_at,
            created_at: now,
            updated_at: now,
        };
        _ = entry.insert(model.clone());
        Ok(Some(model))
    }

    async fn create_verification(
        &self,
        verification: CreateVerification,
    ) -> AuthResult<VerificationView> {
        let now = Utc::now();
        let verification = VerificationView {
            id: uuid::Uuid::new_v4().to_string(),
            identifier: verification.identifier,
            value: verification.value,
            expires_at: verification.expires_at,
            created_at: now,
            updated_at: now,
        };
        _ = self
            .lock()?
            .verifications
            .insert(verification.id.clone(), verification.clone());
        Ok(verification)
    }

    async fn get_verification(
        &self,
        identifier: &str,
        value: &str,
    ) -> AuthResult<Option<VerificationView>> {
        Ok(self
            .lock()?
            .verifications
            .values()
            .filter(|verification| {
                verification.identifier == identifier
                    && verification.value == value
                    && verification.expires_at >= Utc::now()
            })
            .max_by_key(|verification| verification.created_at)
            .cloned())
    }

    async fn get_verification_by_value(&self, value: &str) -> AuthResult<Option<VerificationView>> {
        Ok(self
            .lock()?
            .verifications
            .values()
            .filter(|verification| {
                verification.value == value && verification.expires_at >= Utc::now()
            })
            .max_by_key(|verification| verification.created_at)
            .cloned())
    }

    async fn get_verification_by_identifier(
        &self,
        identifier: &str,
    ) -> AuthResult<Option<VerificationView>> {
        Ok(self
            .lock()?
            .verifications
            .values()
            .filter(|verification| {
                verification.identifier == identifier && verification.expires_at >= Utc::now()
            })
            .max_by_key(|verification| verification.created_at)
            .cloned())
    }

    async fn consume_verification(
        &self,
        identifier: &str,
        value: &str,
    ) -> AuthResult<Option<VerificationView>> {
        let mut state = self.lock()?;
        let found = state
            .verifications
            .values()
            .filter(|verification| verification.identifier == identifier)
            .max_by_key(|verification| verification.created_at)
            .cloned();
        if let Some(verification) = &found {
            if verification.value != value {
                return Ok(None);
            }
            state
                .verifications
                .retain(|_, sibling| sibling.identifier != identifier);
        }
        Ok(found.filter(|verification| verification.expires_at >= Utc::now()))
    }

    async fn get_latest_verification_by_identifier(
        &self,
        identifier: &str,
    ) -> AuthResult<Option<VerificationView>> {
        Ok(self
            .lock()?
            .verifications
            .values()
            .filter(|verification| verification.identifier == identifier)
            .max_by_key(|verification| verification.created_at)
            .cloned())
    }

    async fn consume_verification_by_identifier(
        &self,
        identifier: &str,
    ) -> AuthResult<Option<VerificationView>> {
        let mut state = self.lock()?;
        let found = state
            .verifications
            .values()
            .filter(|verification| verification.identifier == identifier)
            .max_by_key(|verification| verification.created_at)
            .cloned();
        state
            .verifications
            .retain(|_, sibling| sibling.identifier != identifier);
        Ok(found.filter(|verification| verification.expires_at >= Utc::now()))
    }

    async fn delete_verifications_by_identifier(&self, identifier: &str) -> AuthResult<()> {
        self.lock()?
            .verifications
            .retain(|_, verification| verification.identifier != identifier);
        Ok(())
    }

    async fn compare_and_swap_verification(
        &self,
        id: &str,
        expected_value: &str,
        value: &str,
        expires_at: DateTime<Utc>,
    ) -> AuthResult<bool> {
        let mut state = self.lock()?;
        let Some(verification) = state.verifications.get_mut(id) else {
            return Ok(false);
        };
        if verification.value != expected_value {
            return Ok(false);
        }
        value.clone_into(&mut verification.value);
        verification.expires_at = expires_at;
        verification.updated_at = Utc::now();
        Ok(true)
    }

    async fn reserve_verification(&self, verification: CreateVerification) -> AuthResult<bool> {
        let (id, _) = crate::store::verification_reservation_key(&verification.identifier);
        let mut state = self.lock()?;
        let indexmap::map::Entry::Vacant(entry) = state.verifications.entry(id.clone()) else {
            return Ok(false);
        };

        let now = Utc::now();
        _ = entry.insert(VerificationView {
            id,
            identifier: verification.identifier,
            value: verification.value,
            expires_at: verification.expires_at,
            created_at: now,
            updated_at: now,
        });
        Ok(true)
    }

    async fn delete_verification(&self, id: &str) -> AuthResult<()> {
        _ = self.lock()?.verifications.shift_remove(id);
        Ok(())
    }

    async fn delete_expired_verifications(&self) -> AuthResult<usize> {
        let now = Utc::now();
        let mut state = self.lock()?;
        let before = state.verifications.len();
        state.verifications.retain(|_, verification| {
            verification.expires_at.timestamp_millis() >= now.timestamp_millis()
        });
        Ok(before - state.verifications.len())
    }
}