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 SecretError {
    #[error("secret not found")]
    NotFound,
    #[error("storage error: {0}")]
    Storage(String),
    #[error("encryption error: {0}")]
    Encryption(String),
}

/// Represents a factor secret (TOTP key, WebAuthn credential, etc.)
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct SecretRecord {
    pub id: String,
    pub user_id: String,
    pub tenant_id: TenantId,
    pub factor_id: String,
    pub secret_data: Vec<u8>, // Encrypted at rest
    pub metadata: serde_json::Value,
    pub created_at: SystemTime,
    pub updated_at: SystemTime,
}

/// Trait for pluggable secret storage
///
/// Critical: Implementations MUST encrypt secrets at rest
/// Consider using:
/// - HashiCorp Vault
/// - AWS Secrets Manager
/// - Azure Key Vault
/// - GCP Secret Manager
/// - Database with application-level encryption
#[async_trait]
pub trait SecretStore: Send + Sync {
    /// Store a secret (implementation handles encryption)
    async fn set(
        &self,
        user_id: &str,
        tenant_id: &TenantId,
        factor_id: &str,
        secret_data: &[u8],
        metadata: serde_json::Value,
    ) -> Result<String, SecretError>;

    /// Retrieve a secret (implementation handles decryption)
    async fn get(
        &self,
        user_id: &str,
        tenant_id: &TenantId,
        factor_id: &str,
    ) -> Result<Option<SecretRecord>, SecretError>;

    /// Get a specific secret by ID
    async fn get_by_id(&self, id: &str) -> Result<Option<SecretRecord>, SecretError>;

    /// Delete a secret
    async fn delete(&self, id: &str) -> Result<(), SecretError>;

    /// List all secrets for a user/tenant/factor combination
    async fn list(
        &self,
        user_id: &str,
        tenant_id: &TenantId,
        factor_id: Option<&str>,
    ) -> Result<Vec<SecretRecord>, SecretError>;

    /// Rotate encryption keys (for implementations that support it)
    async fn rotate_encryption(&self) -> Result<usize, SecretError> {
        // Default: not supported
        Ok(0)
    }
}