use serde::{Deserialize, Serialize};
pub mod nonce;
pub mod signature {
pub use crate::nonce::signature::*;
}
pub mod storage {
pub use crate::nonce::storage::*;
}
pub use nonce::{
BoxedCleanupStrategy,
CleanupStrategy,
ConfigPreset,
CredentialBuilder,
CredentialVerifier,
CustomCleanupStrategy,
HybridCleanupStrategy,
MacLike,
MemoryStorage,
NonceConfig,
NonceEntry,
NonceError,
NonceGeneratorFn,
NonceStorage,
SignatureAlgorithm,
StorageStats,
TimeProviderFn,
};
#[cfg(feature = "algo-hmac-sha256")]
pub use nonce::{DefaultSignatureAlgorithm, create_default_algorithm};
#[cfg(feature = "metrics")]
pub use nonce::{
ErrorMetrics, InMemoryMetricsCollector, MetricEvent, MetricsCollector, MetricsTimer,
NoOpMetricsCollector, NonceMetrics, PerformanceMetrics,
};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct NonceCredential {
pub timestamp: u64,
pub nonce: String,
pub signature: String,
}
#[cfg(test)]
mod tests {
use crate::{CredentialBuilder, CredentialVerifier, NonceError, storage::MemoryStorage};
use hmac::Mac;
use std::sync::Arc;
#[tokio::test]
async fn test_basic_credential_workflow() {
let secret = b"test_secret";
let payload = b"test_payload";
let storage: Arc<dyn crate::storage::NonceStorage> = Arc::new(MemoryStorage::new());
let credential = CredentialBuilder::new(secret).sign(payload).unwrap();
let result = CredentialVerifier::new(storage)
.with_secret(secret)
.verify(&credential, payload)
.await;
assert!(result.is_ok());
}
#[tokio::test]
async fn test_nonce_replay_protection() {
let secret = b"test_secret";
let payload = b"test_payload";
let storage: Arc<dyn crate::storage::NonceStorage> = Arc::new(MemoryStorage::new());
let credential = CredentialBuilder::new(secret).sign(payload).unwrap();
CredentialVerifier::new(Arc::clone(&storage))
.with_secret(secret)
.verify(&credential, payload)
.await
.unwrap();
let result = CredentialVerifier::new(storage)
.with_secret(secret)
.verify(&credential, payload)
.await;
assert!(matches!(result, Err(NonceError::DuplicateNonce)));
}
#[tokio::test]
async fn test_context_isolation() {
let secret = b"test_secret";
let payload = b"test_payload";
let storage: Arc<dyn crate::storage::NonceStorage> = Arc::new(MemoryStorage::new());
let credential = CredentialBuilder::new(secret).sign(payload).unwrap();
CredentialVerifier::new(Arc::clone(&storage))
.with_secret(secret)
.with_context(Some("context1"))
.verify(&credential, payload)
.await
.unwrap();
let result = CredentialVerifier::new(Arc::clone(&storage))
.with_secret(secret)
.with_context(Some("context2"))
.verify(&credential, payload)
.await;
assert!(result.is_ok());
let result = CredentialVerifier::new(storage)
.with_secret(secret)
.with_context(Some("context1"))
.verify(&credential, payload)
.await;
assert!(matches!(result, Err(NonceError::DuplicateNonce)));
}
#[tokio::test]
async fn test_invalid_signature() {
let payload = b"test_payload";
let storage: Arc<dyn crate::storage::NonceStorage> = Arc::new(MemoryStorage::new());
let credential = CredentialBuilder::new(b"secret1").sign(payload).unwrap();
let result = CredentialVerifier::new(storage)
.with_secret(b"secret2")
.verify(&credential, payload)
.await;
assert!(matches!(result, Err(NonceError::InvalidSignature)));
}
#[tokio::test]
async fn test_structured_signing() {
let secret = b"test_secret";
let storage: Arc<dyn crate::storage::NonceStorage> = Arc::new(MemoryStorage::new());
let components = [b"part1".as_slice(), b"part2", b"part3"];
let credential = CredentialBuilder::new(secret)
.sign_structured(&components)
.unwrap();
let result = CredentialVerifier::new(storage)
.with_secret(secret)
.verify_structured(&credential, &components)
.await;
assert!(result.is_ok());
}
#[tokio::test]
async fn test_custom_signing() {
let secret = b"test_secret";
let storage: Arc<dyn crate::storage::NonceStorage> = Arc::new(MemoryStorage::new());
let credential = CredentialBuilder::new(secret)
.sign_with(|mac, timestamp, nonce| {
mac.update(b"prefix:");
mac.update(timestamp.as_bytes());
mac.update(b":nonce:");
mac.update(nonce.as_bytes());
mac.update(b":custom");
})
.unwrap();
let result = CredentialVerifier::new(storage)
.with_secret(secret)
.verify_with(&credential, |mac| {
mac.update(b"prefix:");
mac.update(credential.timestamp.to_string().as_bytes());
mac.update(b":nonce:");
mac.update(credential.nonce.as_bytes());
mac.update(b":custom");
})
.await;
assert!(result.is_ok());
}
#[tokio::test]
async fn test_serialization() {
let secret = b"test_secret";
let payload = b"test_payload";
let credential = CredentialBuilder::new(secret).sign(payload).unwrap();
let json = serde_json::to_string(&credential).unwrap();
let deserialized: super::NonceCredential = serde_json::from_str(&json).unwrap();
assert_eq!(credential.timestamp, deserialized.timestamp);
assert_eq!(credential.nonce, deserialized.nonce);
assert_eq!(credential.signature, deserialized.signature);
}
#[tokio::test]
async fn test_secret_provider() {
let payload = b"test_payload";
let storage: Arc<dyn crate::storage::NonceStorage> = Arc::new(MemoryStorage::new());
let credential = CredentialBuilder::new(b"user123_secret")
.sign(payload)
.unwrap();
let result = CredentialVerifier::new(storage)
.with_context(Some("user123"))
.with_secret_provider(|context| {
let owned_context = context.map(|s| s.to_owned());
async move {
match owned_context.as_deref() {
Some("user123") => Ok(b"user123_secret".to_vec()),
Some("user456") => Ok(b"user456_secret".to_vec()),
_ => Err(NonceError::CryptoError("Unknown user".to_string())),
}
}
})
.verify(&credential, payload)
.await;
assert!(result.is_ok());
}
}