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 auth_data = client.create_auth_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_auth_data(&auth_data, None, |mac| {
31/// mac.update(auth_data.timestamp.to_string().as_bytes());
32/// mac.update(auth_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<turbosql::Error> for NonceError {
167 /// Converts a `turbosql::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: turbosql::Error) -> Self {
179 if err.to_string().contains("UNIQUE constraint failed") {
180 NonceError::DuplicateNonce
181 } else {
182 NonceError::DatabaseError(err.to_string())
183 }
184 }
185}
186
187#[cfg(test)]
188mod tests {
189 use super::*;
190
191 #[test]
192 fn test_error_display() {
193 assert_eq!(
194 NonceError::DuplicateNonce.to_string(),
195 "Nonce already exists"
196 );
197 assert_eq!(NonceError::ExpiredNonce.to_string(), "Nonce expired");
198 assert_eq!(
199 NonceError::InvalidSignature.to_string(),
200 "Invalid signature"
201 );
202 assert_eq!(
203 NonceError::TimestampOutOfWindow.to_string(),
204 "Timestamp out of window"
205 );
206
207 let db_error = NonceError::DatabaseError("test error".to_string());
208 assert_eq!(db_error.to_string(), "Database error: test error");
209
210 let crypto_error = NonceError::CryptoError("crypto test error".to_string());
211 assert_eq!(crypto_error.to_string(), "Crypto error: crypto test error");
212 }
213
214 #[test]
215 fn test_error_debug() {
216 let error = NonceError::DuplicateNonce;
217 let debug_str = format!("{error:?}");
218 assert_eq!(debug_str, "DuplicateNonce");
219 }
220
221 #[test]
222 fn test_error_is_send_sync() {
223 fn assert_send_sync<T: Send + Sync>() {}
224 assert_send_sync::<NonceError>();
225 }
226
227 #[test]
228 fn test_error_conversion_logic() {
229 // Test the conversion logic by checking string patterns
230 // This tests the From implementation without creating actual turbosql errors
231
232 // Test that UNIQUE constraint errors would be converted to DuplicateNonce
233 let unique_msg = "UNIQUE constraint failed: noncerecord.nonce";
234 assert!(unique_msg.contains("UNIQUE constraint failed"));
235
236 // Test that other errors would be converted to DatabaseError
237 let other_msg = "database is locked";
238 assert!(!other_msg.contains("UNIQUE constraint failed"));
239 }
240}