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