use std::net::IpAddr;
use sha2::{Digest, Sha256};
use crate::config::{AgentConfig, ExposeSecret};
#[derive(Debug, Clone)]
pub struct WebRequestAudit {
pub peer_ip: IpAddr,
pub client_ip: String,
pub bearer_fp: Option<String>,
pub source: &'static str,
}
impl WebRequestAudit {
pub(crate) fn scheduled_placeholder() -> Self {
Self {
peer_ip: IpAddr::from([0, 0, 0, 0]),
client_ip: "scheduled".to_string(),
bearer_fp: None,
source: "scheduled",
}
}
}
fn sha256_hex_prefix(secret: &[u8], prefix_hex_chars: usize) -> String {
let digest = Sha256::digest(secret);
let mut s = String::with_capacity(prefix_hex_chars.min(64));
for b in digest.iter().take(prefix_hex_chars.div_ceil(2)) {
use core::fmt::Write as _;
let _ = write!(&mut s, "{b:02x}");
if s.len() >= prefix_hex_chars {
break;
}
}
s.truncate(prefix_hex_chars.min(s.len()));
s
}
pub(crate) fn web_api_bearer_fingerprint(cfg: &AgentConfig) -> Option<String> {
let raw = cfg.web_api.web_api_bearer_token.expose_secret();
let b = raw.trim().as_bytes();
if b.is_empty() {
return None;
}
Some(sha256_hex_prefix(b, 12))
}
#[cfg(test)]
mod tests {
use secrecy::SecretString;
use super::*;
fn test_cfg(secret: &str) -> AgentConfig {
let mut cfg = crate::config::load_config(None).expect("embed default config");
cfg.web_api.web_api_bearer_token = SecretString::new(secret.to_string().into());
cfg
}
#[test]
fn web_api_bearer_fingerprint_stable_hex12() {
let cfg = test_cfg("integration-test-secret");
let fp = web_api_bearer_fingerprint(&cfg).expect("non-empty secret yields fingerprint");
assert_eq!(fp.len(), 12);
assert!(fp.chars().all(|c| c.is_ascii_hexdigit()));
assert_eq!(
web_api_bearer_fingerprint(&cfg).as_deref(),
Some(fp.as_str())
);
}
}