use futures::lock::Mutex;
use jsonwebtoken::{TokenData, jwk::Jwk};
use serde::{Deserialize, Serialize};
use serde_json::Value;
use tracing::warn;
use url::Url;
use std::{collections::HashMap, sync::Arc};
use super::jwk::JwksCache;
#[derive(Clone, Debug, Default, Deserialize, PartialEq, Eq, Serialize)]
pub struct JwtClaims {
#[serde(alias = "sub")]
pub user_id: String,
#[serde(alias = "iat")]
pub issued_at: u64,
#[serde(alias = "exp")]
pub expiration: u64,
#[serde(alias = "aud")]
#[serde(default)]
#[serde(deserialize_with = "crate::utils::deserialize_one_or_many_strings")]
pub audience: Vec<String>,
#[serde(alias = "iss")]
pub issuer: String,
#[serde(default)]
#[serde(alias = "nbf")]
pub not_before: u64,
#[serde(default)]
#[serde(skip_serializing_if = "String::is_empty")]
#[serde(alias = "jti")]
pub token_id: String,
#[serde(default)]
#[serde(skip_serializing_if = "String::is_empty")]
pub client_id: String,
#[serde(default)]
#[serde(skip_serializing_if = "String::is_empty")]
#[serde(alias = "azp")]
pub authorizing_party: String,
#[serde(default)]
#[serde(flatten)]
pub additional_claims: HashMap<String, Value>,
}
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
pub struct OidcConfig {
pub issuer: Vec<Url>,
pub jwks_url: Url,
pub jwks_cache_ttl: Option<u64>,
pub client_id: Option<String>,
pub client_secret: Option<String>,
pub valid_authorizing_parties: Option<Vec<String>>,
pub valid_audience: Option<Vec<String>>,
#[serde(default)]
pub skip_issuer_validation: bool,
}
impl OidcConfig {
pub fn auth_context_config(&self) -> JwtAuthContextConfig {
if self.valid_audience.is_none() && self.valid_authorizing_parties.is_none() {
warn!(
"Both audience validation and authorizing party validation are disabled. This is insecure."
)
}
JwtAuthContextConfig::new(
JwksCache::new(self.jwks_url.as_str(), self.jwks_cache_ttl.unwrap_or(1800)),
if self.skip_issuer_validation {
warn!("Issuer validation disabled. This is insecure.");
Default::default()
} else {
self.issuer.iter().map(Url::to_string).collect()
},
self.valid_audience.clone().unwrap_or_default(),
self.valid_authorizing_parties.clone().unwrap_or_default(),
)
}
}
#[derive(Debug, Clone)]
pub struct JwtAuthContextConfig {
jwks_cache: Arc<Mutex<JwksCache>>,
valid_issuers: Vec<String>,
valid_audience: Vec<String>,
valid_authorizing_parties: Vec<String>,
}
impl JwtAuthContextConfig {
pub fn new(
jwks_cache: JwksCache,
valid_issuers: Vec<String>,
valid_audience: Vec<String>,
valid_authorizing_parties: Vec<String>,
) -> Self {
Self {
jwks_cache: Arc::new(Mutex::new(jwks_cache)),
valid_issuers,
valid_audience,
valid_authorizing_parties,
}
}
pub async fn lookup_jwk(&self, kid: &str) -> anyhow::Result<Jwk> {
Ok(self.jwks_cache.lock().await.read_jwk(kid).await?)
}
pub async fn verify_token(&self, jwt: &str) -> anyhow::Result<TokenData<JwtClaims>> {
let header = jsonwebtoken::decode_header(jwt)?;
self.lookup_jwk(&header.kid.unwrap_or_default())
.await
.and_then(|jwk| {
self.create_token_decoder(&jwk).map(|decoder| {
let mut validation = jsonwebtoken::Validation::new(header.alg);
validation.validate_aud = false;
if !self.valid_issuers.is_empty() {
validation.set_issuer(&self.valid_issuers);
validation.required_spec_claims.insert("iss".to_string());
}
(decoder, validation)
})
})
.and_then(|(decoder, validation)| {
let token_data = jsonwebtoken::decode::<JwtClaims>(jwt, &decoder, &validation)?;
if !self.valid_audience.is_empty()
&& !token_data
.claims
.audience
.iter()
.any(|aud| self.valid_audience.contains(aud))
{
return Err(anyhow::anyhow!("Invalid JWT Audience"));
}
if !self.valid_authorizing_parties.is_empty()
&& !self
.valid_authorizing_parties
.contains(&token_data.claims.authorizing_party)
{
return Err(anyhow::anyhow!("Invalid Authorizing Party"));
}
Ok(token_data)
})
}
pub fn create_token_decoder(&self, signer: &Jwk) -> anyhow::Result<jsonwebtoken::DecodingKey> {
match signer.algorithm {
jsonwebtoken::jwk::AlgorithmParameters::RSA(ref rsa) => Ok(
jsonwebtoken::DecodingKey::from_rsa_components(&rsa.n, &rsa.e)?,
),
jsonwebtoken::jwk::AlgorithmParameters::EllipticCurve(ref eck) => Ok(
jsonwebtoken::DecodingKey::from_ec_components(&eck.x, &eck.y)?,
),
jsonwebtoken::jwk::AlgorithmParameters::OctetKey(ref oct) => {
Ok(jsonwebtoken::DecodingKey::from_secret(oct.value.as_bytes()))
}
_ => Err(anyhow::anyhow!(
"Unsupported signature key algorithm.".to_owned(),
)),
}
}
}