use sha2::{Digest as _, Sha256};
#[must_use]
pub fn salted_secret_digest(salt: &str, secret: &str) -> String {
let mut hasher = Sha256::new();
hasher.update(salt.as_bytes());
hasher.update(secret.as_bytes());
crate::hex::lower(&hasher.finalize())
}
#[must_use]
pub fn bearer_token(edge_id: &str, secret: &str) -> String {
format!("pc_{edge_id}_{secret}")
}
#[must_use]
pub fn parse_bearer_token(token: &str) -> Option<(&str, &str)> {
let rest = token.strip_prefix("pc_")?;
let (edge_id, secret) = rest.split_once('_')?;
if edge_id.is_empty() || secret.is_empty() {
return None;
}
Some((edge_id, secret))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn salted_secret_digest_matches_fixed_vector() {
assert_eq!(
salted_secret_digest("salty", "s3cr3t"),
"2f0444c13a1f60d9cd0354be4a229d3ec8dcd47c1cb9bc96eb8c4635f94c280a"
);
}
#[test]
fn bearer_token_formats_the_credential_id_and_secret() {
let bearer = bearer_token("edge-a", "test-secret");
assert!(
bearer == "pc_edge-a_test-secret",
"bearer formatter returned an unexpected value"
);
}
#[test]
fn bearer_parser_splits_only_after_the_credential_id() {
assert_eq!(
parse_bearer_token("pc_edge-a_secret_with_underscores"),
Some(("edge-a", "secret_with_underscores"))
);
assert_eq!(parse_bearer_token("not-a-bearer"), None);
assert_eq!(parse_bearer_token("pc__secret"), None);
assert_eq!(parse_bearer_token("pc_edge-a_"), None);
}
}