use base64::{engine::general_purpose::URL_SAFE_NO_PAD, Engine};
use sha2::{Digest, Sha256};
pub const fn redact_password() -> &'static str {
"<password>"
}
pub fn redact_token(token: &str) -> String {
let digest = Sha256::digest(token.as_bytes());
let hex = URL_SAFE_NO_PAD.encode(digest);
let prefix: String = hex.chars().take(8).collect();
format!("<token:…{prefix}>")
}
pub const fn redact_mfa_secret() -> &'static str {
"<mfa-secret>"
}
pub const fn redact_backup_code() -> &'static str {
"<backup-code>"
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn redact_password_returns_fixed_placeholder() {
assert_eq!(redact_password(), "<password>");
}
#[test]
fn redact_token_is_deterministic() {
let a = redact_token("abc123");
let b = redact_token("abc123");
assert_eq!(a, b);
}
#[test]
fn redact_token_changes_per_token() {
assert_ne!(redact_token("aaa"), redact_token("bbb"));
}
#[test]
fn redact_token_reveals_no_substring_of_input() {
let plaintext = "secretSESSIONcookie";
let r = redact_token(plaintext);
for win in plaintext.as_bytes().windows(4) {
let needle = std::str::from_utf8(win).unwrap();
assert!(!r.contains(needle), "redaction leaked {needle:?}: {r}");
}
}
#[test]
fn redact_token_format_is_recognizable() {
let r = redact_token("anything");
assert!(r.starts_with("<token:…"));
assert!(r.ends_with('>'));
assert_eq!(r.len(), "<token:…".len() + 8 + ">".len());
}
#[test]
fn redact_mfa_secret_is_constant() {
assert_eq!(redact_mfa_secret(), "<mfa-secret>");
}
#[test]
fn redact_backup_code_is_constant() {
assert_eq!(redact_backup_code(), "<backup-code>");
}
}