nonce_auth/nonce/storage/
mod.rs

1//! Pluggable storage backends for nonce persistence.
2//!
3//! This module provides a trait-based storage system that allows different
4//! backends to be used for nonce persistence. The available backends depend
5//! on the enabled features.
6
7use crate::NonceError;
8use async_trait::async_trait;
9use std::time::Duration;
10
11// Always available
12mod memory;
13pub use memory::MemoryStorage;
14
15// Feature-gated storage backends
16#[cfg(feature = "sqlite-storage")]
17mod sqlite;
18#[cfg(feature = "sqlite-storage")]
19pub use sqlite::SqliteStorage;
20
21#[cfg(feature = "redis-storage")]
22mod redis;
23#[cfg(feature = "redis-storage")]
24pub use redis::RedisStorage;
25
26/// Represents a stored nonce entry with its metadata.
27#[derive(Debug, Clone)]
28pub struct NonceEntry {
29    /// The unique nonce value
30    pub nonce: String,
31    /// Unix timestamp when the nonce was created
32    pub created_at: i64,
33    /// Optional context for nonce scoping
34    pub context: Option<String>,
35}
36
37/// Statistics about the nonce storage backend.
38#[derive(Debug, Clone)]
39pub struct StorageStats {
40    /// Total number of nonce records in storage
41    pub total_records: usize,
42    /// Additional backend-specific information
43    pub backend_info: String,
44}
45
46/// Abstract storage backend for nonce persistence.
47///
48/// This trait defines the interface that all storage backends must implement
49/// to work with the nonce authentication system. It provides operations for
50/// storing, retrieving, and managing nonces with expiration support.
51///
52/// # Available Implementations
53///
54/// - [`MemoryStorage`] - Always available, in-memory HashMap-based storage
55/// - `SqliteStorage` - Available with `sqlite-storage` feature, persistent SQLite storage
56/// - `RedisStorage` - Available with `redis-storage` feature, distributed Redis storage
57///
58/// # Thread Safety
59///
60/// All methods are async and must be thread-safe. Implementations should
61/// handle concurrent access properly.
62///
63/// # Error Handling
64///
65/// All methods return `Result<T, NonceError>` and should map backend-specific
66/// errors to appropriate `NonceError` variants.
67///
68/// # Example Implementation
69///
70/// ```rust
71/// use nonce_auth::storage::{NonceStorage, NonceEntry, StorageStats};
72/// use nonce_auth::NonceError;
73/// use async_trait::async_trait;
74/// use std::collections::HashMap;
75/// use std::sync::Arc;
76/// use std::time::Duration;
77/// use tokio::sync::RwLock;
78///
79/// #[derive(Default)]
80/// pub struct CustomStorage {
81///     data: Arc<RwLock<HashMap<String, NonceEntry>>>,
82/// }
83///
84/// #[async_trait]
85/// impl NonceStorage for CustomStorage {
86///     async fn init(&self) -> Result<(), NonceError> {
87///         // Initialize storage (create tables, connections, etc.)
88///         Ok(())
89///     }
90///
91///     async fn get(&self, nonce: &str, context: Option<&str>) -> Result<Option<NonceEntry>, NonceError> {
92///         let key = format!("{}:{}", nonce, context.unwrap_or(""));
93///         let data = self.data.read().await;
94///         Ok(data.get(&key).cloned())
95///     }
96///
97///     async fn set(&self, nonce: &str, context: Option<&str>, ttl: Duration) -> Result<(), NonceError> {
98///         let key = format!("{}:{}", nonce, context.unwrap_or(""));
99///         let entry = NonceEntry {
100///             nonce: nonce.to_string(),
101///             created_at: std::time::SystemTime::now()
102///                 .duration_since(std::time::UNIX_EPOCH)
103///                 .unwrap()
104///                 .as_secs() as i64,
105///             context: context.map(|s| s.to_string()),
106///         };
107///         let mut data = self.data.write().await;
108///         if data.contains_key(&key) {
109///             return Err(NonceError::DuplicateNonce);
110///         }
111///         data.insert(key, entry);
112///         Ok(())
113///     }
114///
115///     async fn exists(&self, nonce: &str, context: Option<&str>) -> Result<bool, NonceError> {
116///         let key = format!("{}:{}", nonce, context.unwrap_or(""));
117///         let data = self.data.read().await;
118///         Ok(data.contains_key(&key))
119///     }
120///
121///     async fn cleanup_expired(&self, cutoff_time: i64) -> Result<usize, NonceError> {
122///         let mut data = self.data.write().await;
123///         let initial_count = data.len();
124///         data.retain(|_, entry| entry.created_at > cutoff_time);
125///         Ok(initial_count - data.len())
126///     }
127///
128///     async fn get_stats(&self) -> Result<StorageStats, NonceError> {
129///         let data = self.data.read().await;
130///         Ok(StorageStats {
131///             total_records: data.len(),
132///             backend_info: "Custom storage implementation".to_string(),
133///         })
134///     }
135/// }
136/// ```
137#[async_trait]
138pub trait NonceStorage: Send + Sync {
139    /// Optional method for storage backend initialization.
140    ///
141    /// This method is called once when the storage backend is first used.
142    /// Implementations can use this for tasks like schema creation,
143    /// connection setup, etc.
144    ///
145    /// # Returns
146    ///
147    /// * `Ok(())` - If initialization succeeded
148    /// * `Err(NonceError)` - If initialization failed
149    async fn init(&self) -> Result<(), NonceError> {
150        // Default implementation does nothing
151        Ok(())
152    }
153
154    /// Retrieves a nonce entry if it exists.
155    ///
156    /// # Arguments
157    ///
158    /// * `nonce` - The nonce value to retrieve
159    /// * `context` - Optional context for scoping the nonce
160    ///
161    /// # Returns
162    ///
163    /// * `Ok(Some(NonceEntry))` - If the nonce exists and is not expired
164    /// * `Ok(None)` - If the nonce doesn't exist or has expired
165    /// * `Err(NonceError)` - If there was an error accessing storage
166    async fn get(
167        &self,
168        nonce: &str,
169        context: Option<&str>,
170    ) -> Result<Option<NonceEntry>, NonceError>;
171
172    /// Stores a new nonce with expiration time.
173    ///
174    /// This method should atomically check for duplicates and insert the nonce.
175    /// If the nonce already exists (considering context), it should return
176    /// `NonceError::DuplicateNonce`.
177    ///
178    /// # Arguments
179    ///
180    /// * `nonce` - The nonce value to store
181    /// * `context` - Optional context for scoping the nonce
182    /// * `ttl` - Time-to-live duration for the nonce
183    ///
184    /// # Returns
185    ///
186    /// * `Ok(())` - If the nonce was successfully stored
187    /// * `Err(NonceError::DuplicateNonce)` - If the nonce already exists
188    /// * `Err(NonceError)` - If there was an error accessing storage
189    async fn set(
190        &self,
191        nonce: &str,
192        context: Option<&str>,
193        ttl: Duration,
194    ) -> Result<(), NonceError>;
195
196    /// Checks if a nonce exists without retrieving it.
197    ///
198    /// This is an optimization method for cases where only existence
199    /// checking is needed without the full entry data.
200    ///
201    /// # Arguments
202    ///
203    /// * `nonce` - The nonce value to check
204    /// * `context` - Optional context for scoping the nonce
205    ///
206    /// # Returns
207    ///
208    /// * `Ok(true)` - If the nonce exists and is not expired
209    /// * `Ok(false)` - If the nonce doesn't exist or has expired
210    /// * `Err(NonceError)` - If there was an error accessing storage
211    async fn exists(&self, nonce: &str, context: Option<&str>) -> Result<bool, NonceError>;
212
213    /// Removes all expired nonces from storage.
214    ///
215    /// This method should remove all nonces that were created before
216    /// the specified cutoff time.
217    ///
218    /// # Arguments
219    ///
220    /// * `cutoff_time` - Unix timestamp; nonces created before this time should be removed
221    ///
222    /// # Returns
223    ///
224    /// * `Ok(count)` - Number of nonces that were removed
225    /// * `Err(NonceError)` - If there was an error accessing storage
226    async fn cleanup_expired(&self, cutoff_time: i64) -> Result<usize, NonceError>;
227
228    /// Returns statistics about the storage backend.
229    ///
230    /// This method provides insight into the current state of the storage
231    /// backend, which can be useful for monitoring and debugging.
232    ///
233    /// # Returns
234    ///
235    /// * `Ok(StorageStats)` - Current storage statistics
236    /// * `Err(NonceError)` - If there was an error accessing storage
237    async fn get_stats(&self) -> Result<StorageStats, NonceError>;
238}