pub use fraiseql_jwks::MAX_RESPONSE_BYTES as MAX_JWKS_RESPONSE_BYTES;
use serde::Deserialize;
use crate::security::{
errors::{Result, SecurityError},
oidc::token::{IssuerProfile, OidcValidator},
};
#[derive(Debug, Clone, Deserialize)]
pub struct OidcDiscoveryDocument {
pub issuer: String,
pub jwks_uri: String,
#[serde(default)]
pub id_token_signing_alg_values_supported: Vec<String>,
#[serde(default)]
pub authorization_endpoint: Option<String>,
#[serde(default)]
pub token_endpoint: Option<String>,
}
impl IssuerProfile {
pub(super) async fn get_decoding_key(&self, kid: &str) -> Result<jsonwebtoken::DecodingKey> {
let found = self.jwks.key(kid).await.map_err(|error| {
tracing::error!(error = %error, kid = %kid, "the OIDC key set could not be consulted");
SecurityError::SecurityConfigError(error.to_string())
})?;
let jwk = found.ok_or_else(|| {
tracing::debug!(kid = %kid, "the provider does not publish this key id");
SecurityError::InvalidToken
})?;
jwk.decoding_key().map_err(|error| {
tracing::debug!(error = %error, kid = %kid, "the published key is not usable");
SecurityError::InvalidTokenAlgorithm {
algorithm: jwk.kty.clone(),
}
})
}
}
impl OidcValidator {
pub fn invalidate_jwks_cache(&self) {
for issuer in &self.issuers {
issuer.jwks.invalidate();
}
}
pub async fn refresh_jwks(&self) -> Result<usize> {
let mut count = 0;
for issuer in &self.issuers {
count += issuer
.jwks
.refresh()
.await
.map_err(|error| SecurityError::SecurityConfigError(error.to_string()))?;
}
tracing::info!(key_count = count, "JWKS force-refreshed from provider");
Ok(count)
}
}