nonce_auth/nonce/
error.rs

1use thiserror::Error;
2
3/// Error types that can occur during nonce authentication operations.
4///
5/// This enum represents all possible errors that can occur when using
6/// the nonce authentication library. Each variant corresponds to a
7/// specific failure mode in the authentication process.
8///
9/// # Error Categories
10///
11/// - **Authentication Errors**: `DuplicateNonce`, `ExpiredNonce`, `InvalidSignature`, `TimestampOutOfWindow`
12/// - **System Errors**: `DatabaseError`, `CryptoError`
13///
14/// # Example
15///
16/// ```rust
17/// use nonce_auth::{NonceServer, NonceError, NonceClient};
18/// use hmac::Mac;
19///
20/// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
21/// # NonceServer::init().await?;
22/// let server = NonceServer::new(b"secret", None, None);
23/// let client = NonceClient::new(b"secret");
24/// let protection_data = client.create_protection_data(|mac, timestamp, nonce| {
25///     mac.update(timestamp.as_bytes());
26///     mac.update(nonce.as_bytes());
27/// })?;
28///
29/// // Handle different error types
30/// match server.verify_protection_data(&protection_data, None, |mac| {
31///     mac.update(protection_data.timestamp.to_string().as_bytes());
32///     mac.update(protection_data.nonce.as_bytes());
33/// }).await {
34///     Ok(()) => println!("Request verified"),
35///     Err(NonceError::DuplicateNonce) => println!("Nonce already used"),
36///     Err(NonceError::InvalidSignature) => println!("Invalid signature"),
37///     Err(NonceError::TimestampOutOfWindow) => println!("Request too old"),
38///     Err(e) => println!("Other error: {e}"),
39/// }
40/// # Ok(())
41/// # }
42/// ```
43#[derive(Error, Debug)]
44pub enum NonceError {
45    /// The nonce has already been used and cannot be reused.
46    ///
47    /// This error occurs when a client attempts to use a nonce that has
48    /// already been consumed by the server. This is the primary mechanism
49    /// for preventing replay attacks.
50    ///
51    /// # When This Occurs
52    ///
53    /// - A client sends the same signed request twice
54    /// - A malicious actor attempts to replay a captured request
55    /// - Network issues cause duplicate request delivery
56    ///
57    /// # Resolution
58    ///
59    /// The client should generate a new signed request with a fresh nonce.
60    #[error("Nonce already exists")]
61    DuplicateNonce,
62
63    /// The nonce has expired and is no longer valid.
64    ///
65    /// This error occurs when a nonce exists in the database but has
66    /// exceeded its time-to-live (TTL) duration. Expired nonces are
67    /// considered invalid and should be cleaned up.
68    ///
69    /// # When This Occurs
70    ///
71    /// - A client uses a very old signed request
72    /// - The server's TTL is set too short for the use case
73    /// - There are significant delays in request processing
74    ///
75    /// # Resolution
76    ///
77    /// The client should generate a new signed request with a fresh nonce.
78    #[error("Nonce expired")]
79    ExpiredNonce,
80
81    /// The HMAC signature verification failed.
82    ///
83    /// This error occurs when the provided signature doesn't match the
84    /// expected signature calculated by the server. This indicates either
85    /// a tampered request or mismatched secrets.
86    ///
87    /// # When This Occurs
88    ///
89    /// - Client and server are using different secret keys
90    /// - The request has been tampered with in transit
91    /// - There's a bug in the signature generation/verification logic
92    /// - The timestamp or nonce values have been modified
93    ///
94    /// # Resolution
95    ///
96    /// - Verify that client and server use the same secret key
97    /// - Check for request tampering or transmission errors
98    /// - Ensure proper signature generation on the client side
99    #[error("Invalid signature")]
100    InvalidSignature,
101
102    /// The request timestamp is outside the allowed time window.
103    ///
104    /// This error occurs when the timestamp in the signed request is
105    /// either too old or too far in the future compared to the server's
106    /// current time, exceeding the configured time window.
107    ///
108    /// # When This Occurs
109    ///
110    /// - Client and server clocks are significantly out of sync
111    /// - Network delays cause old requests to arrive late
112    /// - The time window is configured too strictly
113    /// - A malicious actor attempts to use very old captured requests
114    ///
115    /// # Resolution
116    ///
117    /// - Synchronize client and server clocks (e.g., using NTP)
118    /// - Increase the time window if appropriate for your use case
119    /// - Generate fresh requests closer to when they'll be sent
120    #[error("Timestamp out of window")]
121    TimestampOutOfWindow,
122
123    /// A database operation failed.
124    ///
125    /// This error occurs when there's a problem with the underlying
126    /// SQLite database operations, such as connection issues, disk
127    /// space problems, or corruption.
128    ///
129    /// # When This Occurs
130    ///
131    /// - Database file is corrupted or inaccessible
132    /// - Insufficient disk space for database operations
133    /// - Database is locked by another process
134    /// - File permission issues
135    ///
136    /// # Resolution
137    ///
138    /// - Check database file permissions and disk space
139    /// - Verify database file integrity
140    /// - Ensure proper database initialization
141    /// - Check for competing database access
142    #[error("Database error: {0}")]
143    DatabaseError(String),
144
145    /// A cryptographic operation failed.
146    ///
147    /// This error occurs when there's a problem with the HMAC signature
148    /// generation or verification process, typically due to invalid
149    /// key material or system-level crypto issues.
150    ///
151    /// # When This Occurs
152    ///
153    /// - Invalid or corrupted secret key
154    /// - System-level cryptographic library issues
155    /// - Memory allocation failures during crypto operations
156    ///
157    /// # Resolution
158    ///
159    /// - Verify the secret key is valid and properly formatted
160    /// - Check system cryptographic library installation
161    /// - Ensure sufficient system resources
162    #[error("Crypto error: {0}")]
163    CryptoError(String),
164}
165
166impl From<rusqlite::Error> for NonceError {
167    /// Converts a `rusqlite::Error` into a `NonceError`.
168    ///
169    /// This implementation provides automatic conversion from database
170    /// errors to appropriate `NonceError` variants. It specifically
171    /// handles UNIQUE constraint violations (which indicate duplicate
172    /// nonces) and maps other database errors to `DatabaseError`.
173    ///
174    /// # Conversion Rules
175    ///
176    /// - UNIQUE constraint failures → `DuplicateNonce`
177    /// - All other database errors → `DatabaseError`
178    fn from(err: rusqlite::Error) -> Self {
179        match err {
180            rusqlite::Error::SqliteFailure(sqlite_err, _)
181                if sqlite_err.code == rusqlite::ErrorCode::ConstraintViolation
182                    && sqlite_err.extended_code == rusqlite::ffi::SQLITE_CONSTRAINT_UNIQUE =>
183            {
184                NonceError::DuplicateNonce
185            }
186            _ => NonceError::DatabaseError(err.to_string()),
187        }
188    }
189}
190
191#[cfg(test)]
192mod tests {
193    use super::*;
194
195    #[test]
196    fn test_error_display() {
197        assert_eq!(
198            NonceError::DuplicateNonce.to_string(),
199            "Nonce already exists"
200        );
201        assert_eq!(NonceError::ExpiredNonce.to_string(), "Nonce expired");
202        assert_eq!(
203            NonceError::InvalidSignature.to_string(),
204            "Invalid signature"
205        );
206        assert_eq!(
207            NonceError::TimestampOutOfWindow.to_string(),
208            "Timestamp out of window"
209        );
210
211        let db_error = NonceError::DatabaseError("test error".to_string());
212        assert_eq!(db_error.to_string(), "Database error: test error");
213
214        let crypto_error = NonceError::CryptoError("crypto test error".to_string());
215        assert_eq!(crypto_error.to_string(), "Crypto error: crypto test error");
216    }
217
218    #[test]
219    fn test_error_debug() {
220        let error = NonceError::DuplicateNonce;
221        let debug_str = format!("{error:?}");
222        assert_eq!(debug_str, "DuplicateNonce");
223    }
224
225    #[test]
226    fn test_error_is_send_sync() {
227        fn assert_send_sync<T: Send + Sync>() {}
228        assert_send_sync::<NonceError>();
229    }
230
231    #[test]
232    fn test_error_conversion_logic() {
233        // Test the conversion logic by checking error patterns
234        // This tests the From implementation without creating actual rusqlite errors
235
236        // Test that UNIQUE constraint errors would be converted to DuplicateNonce
237        let unique_error = rusqlite::Error::SqliteFailure(
238            rusqlite::ffi::Error::new(rusqlite::ffi::SQLITE_CONSTRAINT_UNIQUE),
239            Some("UNIQUE constraint failed".to_string()),
240        );
241
242        match NonceError::from(unique_error) {
243            NonceError::DuplicateNonce => {}
244            _ => panic!("Expected DuplicateNonce error"),
245        }
246
247        // Test that other errors would be converted to DatabaseError
248        let other_error = rusqlite::Error::SqliteSingleThreadedMode;
249        match NonceError::from(other_error) {
250            NonceError::DatabaseError(_) => {}
251            _ => panic!("Expected DatabaseError"),
252        }
253    }
254}