nonce_auth/nonce/
client.rs1use hmac::Mac;
2use std::marker::PhantomData;
3use std::time::{SystemTime, UNIX_EPOCH};
4
5use super::NonceError;
6use crate::{HmacSha256, NonceCredential};
7
8pub type NonceGenerator = Box<dyn Fn() -> String + Send + Sync>;
10
11pub type TimeProvider = Box<dyn Fn() -> Result<u64, NonceError> + Send + Sync>;
13
14pub struct NonceClient {
39 secret: Vec<u8>,
40 nonce_generator: NonceGenerator,
41 time_provider: TimeProvider,
42}
43
44impl NonceClient {
45 pub fn new(secret: &[u8]) -> Self {
50 Self {
51 secret: secret.to_vec(),
52 nonce_generator: Box::new(|| uuid::Uuid::new_v4().to_string()),
53 time_provider: Box::new(|| {
54 SystemTime::now()
55 .duration_since(UNIX_EPOCH)
56 .map_err(|e| NonceError::CryptoError(format!("System clock error: {e}")))
57 .map(|d| d.as_secs())
58 }),
59 }
60 }
61
62 pub fn builder() -> NonceClientBuilder {
64 NonceClientBuilder::new()
65 }
66
67 pub fn credential_builder(&self) -> NonceCredentialBuilder<'_> {
71 NonceCredentialBuilder::new(self)
72 }
73
74 fn generate_signature<F>(&self, data_builder: F) -> Result<String, NonceError>
76 where
77 F: FnOnce(&mut hmac::Hmac<sha2::Sha256>),
78 {
79 let mut mac = HmacSha256::new_from_slice(&self.secret)
80 .map_err(|e| NonceError::CryptoError(e.to_string()))?;
81 data_builder(&mut mac);
82 let result = mac.finalize();
83 Ok(hex::encode(result.into_bytes()))
84 }
85}
86
87pub struct NonceCredentialBuilder<'a> {
91 client: &'a NonceClient,
92 _phantom: PhantomData<()>,
93}
94
95impl<'a> NonceCredentialBuilder<'a> {
96 fn new(client: &'a NonceClient) -> Self {
97 Self {
98 client,
99 _phantom: PhantomData,
100 }
101 }
102
103 pub fn sign(self, payload: &[u8]) -> Result<NonceCredential, NonceError> {
112 self.sign_with(|mac, timestamp, nonce| {
113 mac.update(timestamp.as_bytes());
114 mac.update(nonce.as_bytes());
115 mac.update(payload);
116 })
117 }
118
119 pub fn sign_with<F>(self, signature_builder: F) -> Result<NonceCredential, NonceError>
130 where
131 F: FnOnce(&mut hmac::Hmac<sha2::Sha256>, &str, &str),
132 {
133 let timestamp = (self.client.time_provider)()?;
134 let nonce = (self.client.nonce_generator)();
135
136 let signature = self.client.generate_signature(|mac| {
137 signature_builder(mac, ×tamp.to_string(), &nonce);
138 })?;
139
140 Ok(NonceCredential {
141 timestamp,
142 nonce,
143 signature,
144 })
145 }
146}
147
148pub struct NonceClientBuilder {
172 secret: Option<Vec<u8>>,
173 nonce_generator: Option<NonceGenerator>,
174 time_provider: Option<TimeProvider>,
175}
176
177impl NonceClientBuilder {
178 pub fn new() -> Self {
180 Self {
181 secret: None,
182 nonce_generator: None,
183 time_provider: None,
184 }
185 }
186
187 pub fn with_secret(mut self, secret: &[u8]) -> Self {
189 self.secret = Some(secret.to_vec());
190 self
191 }
192
193 pub fn with_nonce_generator<F>(mut self, generator: F) -> Self
198 where
199 F: Fn() -> String + Send + Sync + 'static,
200 {
201 self.nonce_generator = Some(Box::new(generator));
202 self
203 }
204
205 pub fn with_time_provider<F>(mut self, provider: F) -> Self
210 where
211 F: Fn() -> Result<u64, NonceError> + Send + Sync + 'static,
212 {
213 self.time_provider = Some(Box::new(provider));
214 self
215 }
216
217 pub fn build(self) -> NonceClient {
223 let secret = self
224 .secret
225 .expect("Secret is required. Use with_secret() to provide one.");
226
227 let nonce_generator = self
228 .nonce_generator
229 .unwrap_or_else(|| Box::new(|| uuid::Uuid::new_v4().to_string()));
230
231 let time_provider = self.time_provider.unwrap_or_else(|| {
232 Box::new(|| {
233 SystemTime::now()
234 .duration_since(UNIX_EPOCH)
235 .map_err(|e| NonceError::CryptoError(format!("System clock error: {e}")))
236 .map(|d| d.as_secs())
237 })
238 });
239
240 NonceClient {
241 secret,
242 nonce_generator,
243 time_provider,
244 }
245 }
246}
247
248impl Default for NonceClientBuilder {
249 fn default() -> Self {
250 Self::new()
251 }
252}
253
254#[cfg(test)]
255mod tests {
256 use super::*;
257
258 const TEST_SECRET: &[u8] = b"test_secret_key_123";
259
260 #[test]
261 fn test_client_creation() {
262 let client = NonceClient::new(TEST_SECRET);
263 assert_eq!(client.secret, TEST_SECRET);
264 }
265
266 #[test]
267 fn test_builder_sign_standard() {
268 let client = NonceClient::new(TEST_SECRET);
269 let payload = b"test payload";
270
271 let credential = client.credential_builder().sign(payload).unwrap();
272
273 assert!(credential.timestamp > 0);
274 assert!(!credential.nonce.is_empty());
275 assert!(!credential.signature.is_empty());
276 assert_eq!(credential.signature.len(), 64);
277
278 let expected_signature = client
280 .generate_signature(|mac| {
281 mac.update(credential.timestamp.to_string().as_bytes());
282 mac.update(credential.nonce.as_bytes());
283 mac.update(payload);
284 })
285 .unwrap();
286 assert_eq!(credential.signature, expected_signature);
287 }
288
289 #[test]
290 fn test_builder_sign_with_custom_logic() {
291 let client = NonceClient::new(TEST_SECRET);
292 let payload = "test payload";
293 let extra = "extra_context";
294
295 let credential = client
296 .credential_builder()
297 .sign_with(|mac, timestamp, nonce| {
298 mac.update(timestamp.as_bytes());
299 mac.update(nonce.as_bytes());
300 mac.update(payload.as_bytes());
301 mac.update(extra.as_bytes());
302 })
303 .unwrap();
304
305 let expected_signature = client
307 .generate_signature(|mac| {
308 mac.update(credential.timestamp.to_string().as_bytes());
309 mac.update(credential.nonce.as_bytes());
310 mac.update(payload.as_bytes());
311 mac.update(extra.as_bytes());
312 })
313 .unwrap();
314 assert_eq!(credential.signature, expected_signature);
315 }
316
317 #[test]
318 fn test_multiple_credentials_different_nonces() {
319 let client = NonceClient::new(TEST_SECRET);
320
321 let credential1 = client.credential_builder().sign(b"payload1").unwrap();
322 let credential2 = client.credential_builder().sign(b"payload2").unwrap();
323
324 assert_ne!(credential1.nonce, credential2.nonce);
325 assert_ne!(credential1.signature, credential2.signature);
326 }
327
328 #[test]
329 fn test_builder_with_secret() {
330 let client = NonceClient::builder().with_secret(TEST_SECRET).build();
331
332 assert_eq!(client.secret, TEST_SECRET);
333 }
334
335 #[test]
336 fn test_builder_with_custom_nonce_generator() {
337 let counter = std::sync::Arc::new(std::sync::atomic::AtomicU32::new(0));
338 let counter_clone = counter.clone();
339
340 let client = NonceClient::builder()
341 .with_secret(TEST_SECRET)
342 .with_nonce_generator(move || {
343 let val = counter_clone.fetch_add(1, std::sync::atomic::Ordering::SeqCst);
344 format!("custom-nonce-{val}")
345 })
346 .build();
347
348 let credential1 = client.credential_builder().sign(b"test").unwrap();
349 let credential2 = client.credential_builder().sign(b"test").unwrap();
350
351 assert_eq!(credential1.nonce, "custom-nonce-0");
352 assert_eq!(credential2.nonce, "custom-nonce-1");
353 }
354
355 #[test]
356 fn test_builder_with_custom_time_provider() {
357 let fixed_time = 1234567890u64;
358
359 let client = NonceClient::builder()
360 .with_secret(TEST_SECRET)
361 .with_time_provider(move || Ok(fixed_time))
362 .build();
363
364 let credential = client.credential_builder().sign(b"test").unwrap();
365 assert_eq!(credential.timestamp, fixed_time);
366 }
367
368 #[test]
369 fn test_builder_with_all_custom_options() {
370 let client = NonceClient::builder()
371 .with_secret(TEST_SECRET)
372 .with_nonce_generator(|| "fixed-nonce".to_string())
373 .with_time_provider(|| Ok(9999999999))
374 .build();
375
376 let credential = client.credential_builder().sign(b"test payload").unwrap();
377
378 assert_eq!(credential.nonce, "fixed-nonce");
379 assert_eq!(credential.timestamp, 9999999999);
380
381 let expected_signature = client
383 .generate_signature(|mac| {
384 mac.update(b"9999999999");
385 mac.update(b"fixed-nonce");
386 mac.update(b"test payload");
387 })
388 .unwrap();
389 assert_eq!(credential.signature, expected_signature);
390 }
391
392 #[test]
393 #[should_panic(expected = "Secret is required")]
394 fn test_builder_without_secret_panics() {
395 NonceClient::builder().build();
396 }
397
398 #[test]
399 fn test_builder_default() {
400 let builder1 = NonceClientBuilder::new();
401 let builder2 = NonceClientBuilder::default();
402
403 assert!(builder1.secret.is_none());
405 assert!(builder1.nonce_generator.is_none());
406 assert!(builder1.time_provider.is_none());
407
408 assert!(builder2.secret.is_none());
409 assert!(builder2.nonce_generator.is_none());
410 assert!(builder2.time_provider.is_none());
411 }
412}