use crate::types::{AuthContext, AuthProvider, AuthType};
use anyhow::{Result, anyhow};
use serde::Deserialize;
use std::{collections::HashMap, fmt::Display};
use subtle::ConstantTimeEq;
#[derive(Hash, Eq, PartialEq)]
pub struct Key {
pub value: String,
}
impl Key {
pub fn new(value: String) -> Self {
Self { value }
}
}
impl From<String> for Key {
fn from(value: String) -> Self {
Self { value }
}
}
impl Display for Key {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "<sensitive key>")
}
}
fn key_from_string<'de, D>(deserializer: D) -> Result<Key, D::Error>
where
D: serde::Deserializer<'de>,
{
let s: String = Deserialize::deserialize(deserializer)?;
Ok(Key::new(s))
}
#[derive(Deserialize)]
pub struct KeyRingEntry {
pub name: String,
#[serde(deserialize_with = "key_from_string")]
pub key: Key,
}
pub type KeyRing = HashMap<Key, String>;
pub fn parse_key_ring(json: &str) -> Result<KeyRing> {
let entries: Vec<KeyRingEntry> = serde_json::from_str(json)?;
let mut ring = KeyRing::new();
for entry in entries {
ring.insert(entry.key, entry.name);
}
Ok(ring)
}
pub struct ApiKeyAuthProvider {
keyring: KeyRing,
}
impl ApiKeyAuthProvider {
pub fn new(keyring: KeyRing) -> Self {
Self { keyring }
}
}
#[async_trait::async_trait]
impl AuthProvider for ApiKeyAuthProvider {
async fn validate_request(
&self,
parts: &dyn crate::types::RequestParts,
) -> Result<AuthContext> {
let token = parts
.bearer_token()
.ok_or_else(|| anyhow!("missing bearer token"))?;
let token_bytes = token.as_bytes();
let mut found: Option<AuthContext> = None;
for (stored_key, name) in &self.keyring {
let stored_bytes = stored_key.value.as_bytes();
let matches = token_bytes.ct_eq(stored_bytes).unwrap_u8() == 1;
if matches {
found = Some(AuthContext {
subject: name.clone(),
email: None,
issuer: "api_key".to_string(),
audience: None,
expires_at: None,
auth_type: AuthType::ApiKey,
is_admin: false,
allow_delegation: true,
});
}
}
found.ok_or_else(|| anyhow!("invalid API token"))
}
}