Skip to main content

darkstrata_credential_check/
lib.rs

1//! DarkStrata Credential Check SDK for Rust
2//!
3//! This SDK provides a secure way to check if credentials have been exposed
4//! in data breaches using k-anonymity privacy protection.
5//!
6//! # Overview
7//!
8//! The SDK sends only a 5 or 6-character hash prefix to the API (k-anonymity),
9//! ensuring that your actual credentials are never exposed. The API returns
10//! all hashes matching the prefix, and the client checks for a match locally
11//! using timing-safe comparison.
12//!
13//! # Quick Start
14//!
15//! ```no_run
16//! use darkstrata_credential_check::{DarkStrataCredentialCheck, ClientOptions};
17//!
18//! #[tokio::main]
19//! async fn main() -> Result<(), Box<dyn std::error::Error>> {
20//!     // Create a client with your API key
21//!     let client = DarkStrataCredentialCheck::new(
22//!         ClientOptions::new("your-api-key")
23//!     )?;
24//!
25//!     // Check a single credential
26//!     let result = client.check("user@example.com", "password123", None).await?;
27//!     if result.found {
28//!         println!("This credential has been compromised!");
29//!     }
30//!
31//!     Ok(())
32//! }
33//! ```
34//!
35//! # Batch Checking
36//!
37//! ```no_run
38//! use darkstrata_credential_check::{DarkStrataCredentialCheck, ClientOptions, Credential};
39//!
40//! #[tokio::main]
41//! async fn main() -> Result<(), Box<dyn std::error::Error>> {
42//!     let client = DarkStrataCredentialCheck::new(
43//!         ClientOptions::new("your-api-key")
44//!     )?;
45//!
46//!     let credentials = vec![
47//!         Credential::new("alice@example.com", "pass1"),
48//!         Credential::new("bob@example.com", "pass2"),
49//!     ];
50//!
51//!     let results = client.check_batch(&credentials, None).await?;
52//!     for (cred, result) in credentials.iter().zip(results.iter()) {
53//!         println!("{}: {}", cred.email, if result.found { "compromised" } else { "safe" });
54//!     }
55//!
56//!     Ok(())
57//! }
58//! ```
59//!
60//! # Configuration Options
61//!
62//! ```no_run
63//! use darkstrata_credential_check::{DarkStrataCredentialCheck, ClientOptions};
64//! use std::time::Duration;
65//!
66//! let client = DarkStrataCredentialCheck::new(
67//!     ClientOptions::new("your-api-key")
68//!         .base_url("https://custom.api.com/v1/")
69//!         .timeout(Duration::from_secs(60))
70//!         .retries(5)
71//!         .enable_caching(true)
72//!         .cache_ttl(Duration::from_secs(1800))
73//! )?;
74//! # Ok::<(), darkstrata_credential_check::DarkStrataError>(())
75//! ```
76//!
77//! # Check Options
78//!
79//! ```no_run
80//! use darkstrata_credential_check::{DarkStrataCredentialCheck, ClientOptions, CheckOptions};
81//!
82//! # async fn example() -> Result<(), Box<dyn std::error::Error>> {
83//! let client = DarkStrataCredentialCheck::new(ClientOptions::new("your-api-key"))?;
84//!
85//! // Filter by date
86//! let result = client.check(
87//!     "user@example.com",
88//!     "password",
89//!     Some(CheckOptions::new().since_epoch_day(19724))
90//! ).await?;
91//!
92//! // Use custom HMAC key
93//! let result = client.check(
94//!     "user@example.com",
95//!     "password",
96//!     Some(CheckOptions::new().client_hmac(
97//!         "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"
98//!     ))
99//! ).await?;
100//! # Ok(())
101//! # }
102//! ```
103//!
104//! # Error Handling
105//!
106//! ```no_run
107//! use darkstrata_credential_check::{DarkStrataCredentialCheck, ClientOptions, DarkStrataError};
108//!
109//! # async fn example() -> Result<(), Box<dyn std::error::Error>> {
110//! let client = DarkStrataCredentialCheck::new(ClientOptions::new("your-api-key"))?;
111//!
112//! match client.check("user@example.com", "password", None).await {
113//!     Ok(result) => {
114//!         if result.found {
115//!             println!("Credential compromised!");
116//!         }
117//!     }
118//!     Err(DarkStrataError::RateLimit { retry_after }) => {
119//!         if let Some(duration) = retry_after {
120//!             println!("Rate limited. Retry after {:?}", duration);
121//!         }
122//!     }
123//!     Err(DarkStrataError::Authentication { .. }) => {
124//!         println!("Invalid API key");
125//!     }
126//!     Err(e) if e.is_retryable() => {
127//!         println!("Transient error, can retry: {}", e);
128//!     }
129//!     Err(e) => {
130//!         println!("Error: {}", e);
131//!     }
132//! }
133//! # Ok(())
134//! # }
135//! ```
136
137#![warn(missing_docs)]
138#![warn(rustdoc::missing_crate_level_docs)]
139
140mod client;
141mod constants;
142mod crypto;
143mod errors;
144mod types;
145
146// Re-export main client
147pub use client::DarkStrataCredentialCheck;
148
149// Re-export types
150pub use types::{
151    CheckMetadata, CheckOptions, CheckResult, ClientOptions, Credential, CredentialInfo,
152    HmacSource, SinceFilter,
153};
154
155// Re-export errors
156pub use errors::{is_retryable_status, DarkStrataError, Result};
157
158/// Cryptographic utilities for advanced usage.
159///
160/// These functions are exposed for users who want to pre-compute hashes
161/// or implement custom processing logic.
162pub mod crypto_utils {
163    pub use crate::crypto::{
164        extract_prefix, extract_prefix_with_length, hash_credential, hmac_sha256, is_hash_in_set,
165        is_valid_hash, is_valid_prefix, sha256, validate_client_hmac,
166    };
167}
168
169/// Configuration constants.
170///
171/// These are exposed for users who want to reference default values
172/// or implement custom logic that aligns with the SDK's behavior.
173pub mod config {
174    pub use crate::constants::{
175        response_headers, retry, API_KEY_HEADER, CREDENTIAL_CHECK_ENDPOINT, DEFAULT_BASE_URL,
176        DEFAULT_CACHE_TTL, DEFAULT_RETRIES, DEFAULT_TIMEOUT, MAX_PREFIX_LENGTH,
177        MIN_CLIENT_HMAC_LENGTH, MIN_PREFIX_LENGTH, PREFIX_LENGTH, SHA256_HEX_LENGTH,
178        TIME_WINDOW_SECONDS, USER_AGENT,
179    };
180}
181
182#[cfg(test)]
183mod tests {
184    use super::*;
185
186    #[test]
187    fn test_public_exports() {
188        // Verify main types are exported
189        let _ = ClientOptions::new("test");
190        let _ = CheckOptions::new();
191        let _ = Credential::new("test@example.com", "password");
192
193        // Verify error types
194        let err = DarkStrataError::validation("test");
195        assert!(!err.is_retryable());
196
197        // Verify crypto utils
198        let hash = crypto_utils::sha256("test");
199        assert_eq!(hash.len(), 64);
200
201        // Verify constants
202        assert_eq!(config::PREFIX_LENGTH, 5);
203        assert_eq!(config::MIN_PREFIX_LENGTH, 5);
204        assert_eq!(config::MAX_PREFIX_LENGTH, 6);
205    }
206}