use crate::errors::{AuthError, Result};
use sha1::{Digest, Sha1};
pub async fn is_password_breached(password: &str) -> Result<bool> {
let hash = hex::encode(Sha1::digest(password.as_bytes())).to_uppercase();
let (prefix, suffix) = hash.split_at(5);
let url = format!("https://api.pwnedpasswords.com/range/{prefix}");
let response = reqwest::Client::builder()
.timeout(std::time::Duration::from_secs(5))
.build()
.map_err(|e| AuthError::internal(format!("HTTP client error: {e}")))?
.get(&url)
.header("Add-Padding", "true")
.send()
.await
.map_err(|e| AuthError::internal(format!("HIBP API request failed: {e}")))?;
if !response.status().is_success() {
return Err(AuthError::internal(format!(
"HIBP API returned status {}",
response.status()
)));
}
let body = response
.text()
.await
.map_err(|e| AuthError::internal(format!("Failed to read HIBP response: {e}")))?;
let breached = body.lines().any(|line| line.trim().starts_with(suffix));
Ok(breached)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn sha1_prefix_suffix_split() {
let hash = hex::encode(Sha1::digest(b"password")).to_uppercase();
assert_eq!(&hash[..5], "5BAA6");
assert_eq!(hash.len(), 40);
}
}