uvb-storage-api 0.2.1

Storage backend trait abstractions for UVB data persistence
Documentation
use async_trait::async_trait;
use serde::{Deserialize, Serialize};
use std::time::SystemTime;
use thiserror::Error;
use uvb_core::TenantId;

#[derive(Debug, Error)]
pub enum EnrollmentError {
    #[error("enrollment not found")]
    NotFound,
    #[error("already enrolled")]
    AlreadyEnrolled,
    #[error("storage error: {0}")]
    Storage(String),
}

/// Tracks which factors a user has enrolled in
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct EnrollmentRecord {
    pub id: String,
    pub user_id: String,
    pub tenant_id: TenantId,
    pub factor_id: String,
    pub status: EnrollmentStatus,
    pub display_name: Option<String>, // e.g., "iPhone 15", "YubiKey 5"
    pub metadata: serde_json::Value,
    pub enrolled_at: SystemTime,
    pub last_used_at: Option<SystemTime>,
    pub use_count: u64,
}

#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
pub enum EnrollmentStatus {
    Active,
    Suspended,
    Revoked,
}

/// Trait for pluggable enrollment storage
#[async_trait]
pub trait EnrollmentStore: Send + Sync {
    /// Create a new enrollment
    async fn create(&self, record: EnrollmentRecord) -> Result<String, EnrollmentError>;

    /// Get a specific enrollment by ID
    async fn get(&self, id: &str) -> Result<Option<EnrollmentRecord>, EnrollmentError>;

    /// Check if user is enrolled in a specific factor
    async fn is_enrolled(
        &self,
        user_id: &str,
        tenant_id: &TenantId,
        factor_id: &str,
    ) -> Result<bool, EnrollmentError>;

    /// List all enrollments for a user
    async fn list_by_user(
        &self,
        user_id: &str,
        tenant_id: &TenantId,
    ) -> Result<Vec<EnrollmentRecord>, EnrollmentError>;

    /// Update enrollment (e.g., status change, last_used)
    async fn update(&self, record: EnrollmentRecord) -> Result<(), EnrollmentError>;

    /// Delete/revoke an enrollment
    async fn delete(&self, id: &str) -> Result<(), EnrollmentError>;

    /// Record usage of a factor (increment counter, update timestamp)
    async fn record_usage(&self, id: &str) -> Result<(), EnrollmentError>;
}