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