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::{Duration, SystemTime};
use thiserror::Error;
use uvb_core::TenantId;

#[derive(Debug, Error)]
pub enum SessionError {
    #[error("session not found")]
    NotFound,
    #[error("session expired")]
    Expired,
    #[error("storage error: {0}")]
    Storage(String),
}

/// Represents an authenticated session after successful verification
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct SessionRecord {
    pub id: String,
    pub user_id: String,
    pub tenant_id: TenantId,
    pub transaction_id: String,
    pub factors_used: Vec<String>,
    pub assurance_level: i32,
    pub metadata: serde_json::Value,
    pub created_at: SystemTime,
    pub expires_at: SystemTime,
    pub last_activity_at: SystemTime,
}

/// Trait for pluggable session storage
///
/// Ideal for:
/// - Redis (with TTL)
/// - Memcached
/// - PostgreSQL (with periodic cleanup)
/// - JWT (stateless, no storage)
#[async_trait]
pub trait SessionStore: Send + Sync {
    /// Create a new session
    async fn create(&self, record: SessionRecord) -> Result<String, SessionError>;

    /// Get a session by ID
    async fn get(&self, id: &str) -> Result<Option<SessionRecord>, SessionError>;

    /// Update session (e.g., extend expiration, update metadata)
    async fn update(&self, record: SessionRecord) -> Result<(), SessionError>;

    /// Delete a session (logout)
    async fn delete(&self, id: &str) -> Result<(), SessionError>;

    /// Delete all sessions for a user (force logout)
    async fn delete_by_user(
        &self,
        user_id: &str,
        tenant_id: &TenantId,
    ) -> Result<usize, SessionError>;

    /// Extend session expiration (sliding window)
    async fn extend(&self, id: &str, duration: Duration) -> Result<(), SessionError>;

    /// Update last activity timestamp
    async fn touch(&self, id: &str) -> Result<(), SessionError>;

    /// Cleanup expired sessions (background job)
    async fn cleanup_expired(&self) -> Result<usize, SessionError>;

    /// List active sessions for a user
    async fn list_by_user(
        &self,
        user_id: &str,
        tenant_id: &TenantId,
    ) -> Result<Vec<SessionRecord>, SessionError>;
}