use std::sync::Arc;
use jsonwebtoken::{decode, decode_header, Algorithm, DecodingKey, Validation};
use serde_json::Value;
use super::jwks::JwksCache;
use crate::config::JwtConfig;
enum KeySource {
Pem(Arc<DecodingKey>),
Jwks(JwksCache),
}
pub(crate) struct ConfigVerifier {
keys: KeySource,
issuer: Option<String>,
audience: Option<String>,
}
impl ConfigVerifier {
pub(crate) fn build(jwt: &JwtConfig) -> Result<Self, String> {
super::crypto::install_default_crypto_provider();
let keys = if let Some(uri) = &jwt.jwks_uri {
KeySource::Jwks(JwksCache::new(uri.clone()))
} else if let Some(pem_path) = &jwt.public_key_pem_file {
let pem = std::fs::read(pem_path)
.map_err(|e| format!("failed to read auth.jwt.public_key_pem_file: {e}"))?;
let key = DecodingKey::from_ed_pem(&pem)
.map_err(|e| format!("invalid Ed25519 public key PEM: {e}"))?;
KeySource::Pem(Arc::new(key))
} else {
return Err("auth.jwt requires either jwks_uri or public_key_pem_file".to_string());
};
Ok(Self {
keys,
issuer: jwt.issuer.clone(),
audience: jwt.audience.clone(),
})
}
pub(crate) async fn verify(&self, token: &str) -> Option<Value> {
let header = decode_header(token).ok()?;
let (key, algorithm) = match &self.keys {
KeySource::Pem(k) => (k.clone(), Algorithm::EdDSA),
KeySource::Jwks(cache) => {
let kid = header.kid.as_deref()?;
let vk = cache.key_for(kid).await?;
(vk.key, vk.algorithm)
}
};
if header.alg != algorithm {
return None;
}
let mut validation = Validation::new(algorithm);
if let Some(iss) = &self.issuer {
validation.set_issuer(&[iss]);
}
match &self.audience {
Some(aud) => validation.set_audience(&[aud]),
None => validation.validate_aud = false,
}
decode::<Value>(token, &key, &validation)
.ok()
.map(|data| data.claims)
}
}