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}