use subtle::ConstantTimeEq;
use crate::config::ApiKeyConfig;
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum AuthResult {
Open,
Allowed,
MissingKey,
InvalidKey,
InsufficientPermission,
}
impl AuthResult {
pub fn is_allowed(&self) -> bool {
matches!(self, AuthResult::Open | AuthResult::Allowed)
}
pub fn error_message(&self, permission: &str) -> Option<String> {
match self {
AuthResult::Open | AuthResult::Allowed => None,
AuthResult::MissingKey => Some("Missing API key".to_string()),
AuthResult::InvalidKey => Some("Invalid API key".to_string()),
AuthResult::InsufficientPermission => {
Some(format!("API key does not have '{permission}' permission"))
}
}
}
}
pub fn extract_bearer_from_value(value: &str) -> Option<&str> {
value.strip_prefix("Bearer ")
}
pub fn check_auth(
api_keys: &[ApiKeyConfig],
provided_key: Option<&str>,
permission: &str,
) -> AuthResult {
if api_keys.is_empty() {
return AuthResult::Open;
}
let key = match provided_key {
Some(k) if !k.is_empty() => k,
_ => return AuthResult::MissingKey,
};
match api_keys.iter().find(|k| {
let a = k.key.as_bytes();
let b = key.as_bytes();
a.len() == b.len() && a.ct_eq(b).into()
}) {
None => AuthResult::InvalidKey,
Some(k) if !k.has_permission(permission) => AuthResult::InsufficientPermission,
Some(_) => AuthResult::Allowed,
}
}
#[cfg(test)]
mod tests {
use super::*;
fn test_keys() -> Vec<ApiKeyConfig> {
vec![
ApiKeyConfig {
key: "rw-key".to_string(),
name: "Read-Write".to_string(),
permissions: vec!["read".to_string(), "write".to_string()],
},
ApiKeyConfig {
key: "ro-key".to_string(),
name: "Read-Only".to_string(),
permissions: vec!["read".to_string()],
},
]
}
#[test]
fn test_empty_keys_allows_all() {
assert_eq!(check_auth(&[], Some("anything"), "write"), AuthResult::Open);
assert_eq!(check_auth(&[], None, "read"), AuthResult::Open);
}
#[test]
fn test_missing_key() {
let keys = test_keys();
assert_eq!(check_auth(&keys, None, "read"), AuthResult::MissingKey);
assert_eq!(check_auth(&keys, Some(""), "read"), AuthResult::MissingKey);
}
#[test]
fn test_invalid_key() {
let keys = test_keys();
assert_eq!(
check_auth(&keys, Some("bad-key"), "read"),
AuthResult::InvalidKey
);
}
#[test]
fn test_insufficient_permission() {
let keys = test_keys();
assert_eq!(
check_auth(&keys, Some("ro-key"), "write"),
AuthResult::InsufficientPermission
);
}
#[test]
fn test_valid_read() {
let keys = test_keys();
assert_eq!(
check_auth(&keys, Some("ro-key"), "read"),
AuthResult::Allowed
);
assert_eq!(
check_auth(&keys, Some("rw-key"), "read"),
AuthResult::Allowed
);
}
#[test]
fn test_valid_write() {
let keys = test_keys();
assert_eq!(
check_auth(&keys, Some("rw-key"), "write"),
AuthResult::Allowed
);
}
}