sos_protocol/
hashcheck.rs

1//! Check password hashes using the hashcheck service.
2use super::{is_offline, Result};
3use tracing::instrument;
4
5/// Default endpoint for HIBP database checks.
6const ENDPOINT: &str = "https://hashcheck.saveoursecrets.com";
7
8/// Check a single SHA1 hash of a password exists in the HIBP
9/// database by calling an endpoint.
10#[instrument(skip_all)]
11pub async fn single(
12    password_hash: String,
13    host: Option<String>,
14) -> Result<bool> {
15    if is_offline() {
16        tracing::warn!("offline mode active, ignoring hashcheck");
17        return Ok(false);
18    }
19
20    let host = host.unwrap_or_else(|| ENDPOINT.to_owned());
21    tracing::info!(host = %host, "hashcheck");
22    let url = format!("{}/{}", host, password_hash.to_uppercase());
23    let client = reqwest::Client::new();
24    let res = client.get(url).send().await?;
25    tracing::debug!(status = %res.status(), "hashcheck");
26    let res = res.error_for_status()?;
27    let value = res.json::<u8>().await?;
28    let result = if value == 1 { true } else { false };
29    Ok(result)
30}
31
32/// Check a collection of SHA1 hashes.
33#[instrument(skip_all)]
34pub async fn batch(
35    hashes: &[String],
36    host: Option<String>,
37) -> Result<Vec<bool>> {
38    if is_offline() {
39        tracing::warn!("offline mode active, ignoring batch hashcheck");
40        return Ok(hashes.iter().map(|_| false).collect());
41    }
42
43    let host = host.unwrap_or_else(|| ENDPOINT.to_owned());
44
45    tracing::info!(host = %host, "hashcheck");
46
47    let url = format!("{}/", host);
48    let client = reqwest::Client::new();
49    let res = client.post(url).json(&hashes).send().await?;
50    tracing::debug!(status = %res.status(), "hashcheck");
51    let res = res.error_for_status()?;
52    let value = res.json::<Vec<u8>>().await?;
53    let result = value
54        .into_iter()
55        .map(|value| if value == 1 { true } else { false })
56        .collect();
57    Ok(result)
58}