use anyhow::Context; use axum::extract::FromRef; use openidconnect::IntrospectionUrl; use zitadel::{ axum::introspection::IntrospectionStateBuilderError, credentials::Application, oidc::{discovery::discover, introspection::AuthorityAuthentication}, }; #[derive(Clone, Debug)] pub struct IntrospectionState { pub(crate) config: IntrospectionConfig, } #[derive(clap::Args, Clone, Debug, PartialEq, Eq)] pub struct IntrospectionConfigClap { #[arg( env = "ZITADEL_AUTHORITY", long = "zitadel-authority", group = "introspection" )] pub authority: String, #[arg( env = "ZITADEL_CLIENT_ID", long = "zitadel-client-id", group = "introspection" )] pub client_id: String, #[arg( env = "ZITADEL_CLIENT_SECRET", long = "zitadel-client-secret", group = "introspection" )] pub client_secret: String, } impl IntrospectionConfigClap { async fn try_into(self) -> anyhow::Result { IntrospectionStateBuilder::new(&self.authority) .with_basic_auth(&self.client_id, &self.client_secret) .build() .await .context("failed to generate an introspection builder") } } /// Configuration that must be inject into the axum application state. Used by the /// [IntrospectionStateBuilder](super::IntrospectionStateBuilder). This struct is also used to create the [IntrospectionState](IntrospectionState) #[derive(Debug, Clone)] pub struct IntrospectionConfig { pub authority: String, pub authentication: AuthorityAuthentication, pub introspection_uri: IntrospectionUrl, } impl FromRef for IntrospectionConfig { fn from_ref(input: &IntrospectionState) -> Self { input.config.clone() } } pub struct IntrospectionStateBuilder { authority: String, authentication: Option, } /// Builder for [IntrospectionConfig] impl IntrospectionStateBuilder { pub fn new(authority: &str) -> Self { Self { authority: authority.to_string(), authentication: None, } } pub fn with_basic_auth( &mut self, client_id: &str, client_secret: &str, ) -> &mut IntrospectionStateBuilder { self.authentication = Some(AuthorityAuthentication::Basic { client_id: client_id.to_string(), client_secret: client_secret.to_string(), }); self } pub fn with_jwt_profile(&mut self, application: Application) -> &mut IntrospectionStateBuilder { self.authentication = Some(AuthorityAuthentication::JWTProfile { application }); self } pub async fn build(&mut self) -> Result { let authentication = self .authentication .clone() .ok_or(IntrospectionStateBuilderError::NoAuthSchema)?; let metadata = discover(&self.authority) .await .map_err(|source| IntrospectionStateBuilderError::Discovery { source })?; let introspection_uri = metadata .additional_metadata() .introspection_endpoint .clone() .ok_or(IntrospectionStateBuilderError::NoIntrospectionUrl)?; Ok(IntrospectionState { config: IntrospectionConfig { authority: self.authority.clone(), introspection_uri: introspection_uri, authentication: authentication, }, }) } }