pub mod crypto; pub mod database; pub mod redis_store; use async_trait::async_trait; use serde::{Deserialize, Serialize}; use crate::config::AppConfig; #[derive(Debug, Clone, Serialize)] pub struct SessionPersistenceDiagnostics { pub backend: String, pub status: String, pub active_sessions: usize, pub unique_users: usize, pub unique_browsers: usize, pub latest_accessed_unix: Option, pub truncated: bool, pub engine_version: Option, pub connected_clients: Option, pub blocked_clients: Option, pub used_memory_bytes: Option, pub server_uptime_secs: Option, pub role: Option, pub master_link_status: Option, } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct PersistedSession { pub token: String, pub account_id: String, pub browser_id: String, pub email: String, pub password_ciphertext: String, pub user_hash: String, pub imap_host: String, pub imap_port: u16, pub imap_tls: bool, pub imap_tls_verify: bool, pub smtp_host: String, pub smtp_port: u16, pub smtp_tls: bool, pub smtp_tls_verify: bool, pub timeout_secs: u64, pub created_at_unix: u64, pub last_accessed_unix: u64, pub user_agent: String, } impl PersistedSession { pub fn expired_at(&self, now_unix: u64) -> bool { now_unix.saturating_sub(self.last_accessed_unix) > self.timeout_secs } } #[async_trait] pub trait SessionPersistence: Send + Sync { async fn save(&self, session: &PersistedSession) -> Result<(), String>; async fn load_token(&self, token: &str) -> Result, String>; async fn load_browser(&self, browser_id: &str) -> Result, String>; async fn load_user(&self, user_hash: &str) -> Result, String>; async fn browser_exists(&self, browser_id: &str) -> Result; async fn delete_account(&self, account_id: &str) -> Result; async fn delete_browser(&self, browser_id: &str) -> Result<(), String>; async fn purge_expired(&self, now_unix: u64) -> Result; async fn diagnostics(&self, now_unix: u64) -> Result; } pub async fn from_config( config: &AppConfig, ) -> Result, String> { match config.session_backend.as_str() { "database" => Ok(std::sync::Arc::new( database::DatabaseSessionStore::connect(config).await?, )), "redis" => Ok(std::sync::Arc::new( redis_store::RedisSessionStore::connect(config).await?, )), other => Err(format!("unsupported session backend: {other}")), } }