fn constant_time_eq(a: &str, b: &str) -> bool {
if a.len() != b.len() {
let mut _dummy: u8 = 0;
for b in a.bytes() {
_dummy |= b;
}
return false;
}
let mut result: u8 = 0;
for (xa, xb) in a.bytes().zip(b.bytes()) {
result |= xa ^ xb;
}
result == 0
}
pub fn validate_api_key(provided: &str, allowed_keys: &[String]) -> bool {
if allowed_keys.is_empty() {
return true;
}
allowed_keys.iter().any(|k| constant_time_eq(provided, k))
}
pub fn parse_api_keys(env_val: &str) -> Vec<String> {
env_val
.split(',')
.map(|s| s.trim().to_owned())
.filter(|s| !s.is_empty())
.collect()
}
pub fn generate_token() -> String {
use rand::RngExt;
let bytes: [u8; 16] = rand::rng().random();
let hex: String = bytes.iter().map(|b| format!("{b:02x}")).collect();
format!("epis-{hex}")
}
pub fn is_localhost(host: &str) -> bool {
matches!(host, "127.0.0.1" | "::1" | "localhost")
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn empty_allowed_means_no_auth() {
assert!(validate_api_key("anything", &[]));
}
#[test]
fn valid_key_accepted() {
let keys: Vec<String> = vec!["abc123".into(), "def456".into()];
assert!(validate_api_key("abc123", &keys));
}
#[test]
fn invalid_key_rejected() {
let keys: Vec<String> = vec!["abc123".into()];
assert!(!validate_api_key("wrong", &keys));
}
#[test]
fn parse_keys_splits_and_trims() {
let parsed = parse_api_keys(" key1 , key2 ,, key3 ");
assert_eq!(parsed, vec!["key1", "key2", "key3"]);
}
#[test]
fn parse_keys_empty_string() {
let parsed = parse_api_keys("");
assert!(parsed.is_empty());
}
#[test]
fn parse_keys_only_commas() {
let parsed = parse_api_keys(",,,");
assert!(parsed.is_empty());
}
#[test]
fn generate_token_format() {
let token = generate_token();
assert!(token.starts_with("epis-"));
assert_eq!(token.len(), 5 + 32); let hex_part = &token[5..];
assert!(hex_part.chars().all(|c| c.is_ascii_hexdigit()));
}
#[test]
fn generate_tokens_are_unique() {
let a = generate_token();
let b = generate_token();
assert_ne!(a, b);
}
#[test]
fn constant_time_eq_matches() {
assert!(constant_time_eq("hello", "hello"));
assert!(!constant_time_eq("hello", "world"));
assert!(!constant_time_eq("short", "muchlonger"));
}
#[test]
fn is_localhost_checks() {
assert!(is_localhost("127.0.0.1"));
assert!(is_localhost("::1"));
assert!(is_localhost("localhost"));
assert!(!is_localhost("0.0.0.0"));
assert!(!is_localhost("192.168.1.1"));
}
}