Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve introspection cache #567

Merged
merged 8 commits into from
Aug 27, 2024
4 changes: 3 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ interceptors = ["credentials", "dep:time", "dep:tokio"]
## By default, only the in-memory cache is available. To use a different cache,
## enable specific features of this crate, or implement your own cache with
## the trait.
introspection_cache = ["dep:async-trait", "dep:time"]
introspection_cache = ["dep:async-trait", "dep:time", "dep:moka"]

## The OIDC module enables basic OIDC (OpenID Connect) features to communicate
## with ZITADEL. Two examples are the `discover` and `introspect` functions.
Expand Down Expand Up @@ -146,6 +146,7 @@ base64-compat = { version = "1", optional = true }
custom_error = "1.9.2"
document-features = { version = "0.2.8", optional = true }
jsonwebtoken = { version = "9.3.0", optional = true }
moka = { version = "0.12.8", features = ["future"], optional = true }
openidconnect = { version = "3.5.0", optional = true }
pbjson-types = { version = "0.7.0", optional = true }
prost = { version = "0.13.1", optional = true }
Expand All @@ -170,6 +171,7 @@ tonic-types = { version = "0.12.1", optional = true }
chrono = "0.4.38"
tokio = { version = "1.37.0", features = ["macros", "rt-multi-thread"] }
tower = { version = "0.4.13" }
http-body-util = "0.1.0"

[package.metadata.docs.rs]
all-features = true
7 changes: 4 additions & 3 deletions src/axum/introspection/mod.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
//! The intropection module allows you to use the OAuth 2.0 Token Introspection flow to authenticate users against ZITADEL.
//! The introspection module allows you to use the OAuth 2.0 Token Introspection flow to authenticate users against ZITADEL.
//!
//! Axum uses "extracters" and "middlewares" to intercept calls. To authenticate a user against ZITADEL, you can use the [IntrospectedUser].
//! Axum uses "extractors" and "middlewares" to intercept calls. To authenticate a user against ZITADEL, you can use the [IntrospectedUser].
//! Which enables an extractor workflow: [extractor](https://docs.rs/axum/latest/axum/extract/index.html)
//!
//! #### Configure Axum
//!
//! To use the introspection flow, you need to configure the [IntrospectionState] and add it to your [Router](https://docs.rs/axum/latest/axum/routing/struct.Router.html).
//! When a custom state is used, [FromRef](axum::extract::FromRef) must be implemented. See [IntrospectionState] for more Details.
//!
//! ```no_run
//! #
Expand Down Expand Up @@ -60,6 +61,6 @@ mod state;
mod state_builder;
mod user;

pub use state::{IntrospectionConfig, IntrospectionState};
pub use state::IntrospectionState;
pub use state_builder::{IntrospectionStateBuilder, IntrospectionStateBuilderError};
pub use user::{IntrospectedUser, IntrospectionGuardError};
45 changes: 27 additions & 18 deletions src/axum/introspection/state.rs
Original file line number Diff line number Diff line change
@@ -1,30 +1,39 @@
use axum::extract::FromRef;
use openidconnect::IntrospectionUrl;
use std::sync::Arc;

#[cfg(feature = "introspection_cache")]
use crate::oidc::introspection::cache::IntrospectionCache;
use crate::oidc::introspection::AuthorityAuthentication;

/// State which must be present for extractor to work,
/// compare [axum's official documentation](https://docs.rs/axum/0.6.4/axum/extract/struct.State.html#for-library-authors).
/// Use [IntrospectionStateBuilder](super::IntrospectionStateBuilder) to configure the respective parameters.
///
/// If a custom state is used, then [FromRef](axum::extract::FromRef) must be implemented,
/// to make the necessary state available.
///
/// ```
/// use axum::extract::FromRef;
/// use zitadel::axum::introspection::IntrospectionState;
/// struct UserState {
/// introspection_state: IntrospectionState
/// }
///
/// impl FromRef<UserState> for IntrospectionState {
/// fn from_ref(input: &UserState) -> Self {
/// input.introspection_state.clone()
/// }
/// }
#[derive(Clone, Debug)]
pub struct IntrospectionState {
pub(crate) config: IntrospectionConfig,
pub(crate) config: Arc<IntrospectionConfig>,
}

impl IntrospectionState {
pub fn config(&self) -> &IntrospectionConfig {
&self.config
}
}

/// 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 {
#[derive(Debug)]
pub(crate) struct IntrospectionConfig {
pub(crate) authority: String,
pub(crate) authentication: AuthorityAuthentication,
pub(crate) introspection_uri: IntrospectionUrl,
}

impl FromRef<IntrospectionState> for IntrospectionConfig {
fn from_ref(input: &IntrospectionState) -> Self {
input.config.clone()
}
#[cfg(feature = "introspection_cache")]
pub(crate) cache: Option<Box<dyn IntrospectionCache>>,
}
25 changes: 23 additions & 2 deletions src/axum/introspection/state_builder.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
use custom_error::custom_error;
use std::sync::Arc;

use crate::axum::introspection::state::IntrospectionConfig;
use crate::credentials::Application;
use crate::oidc::discovery::{discover, DiscoveryError};
use crate::oidc::introspection::AuthorityAuthentication;

#[cfg(feature = "introspection_cache")]
use crate::oidc::introspection::cache::IntrospectionCache;

use super::state::IntrospectionState;

custom_error! {
Expand All @@ -18,6 +22,8 @@ custom_error! {
pub struct IntrospectionStateBuilder {
authority: String,
authentication: Option<AuthorityAuthentication>,
#[cfg(feature = "introspection_cache")]
cache: Option<Box<dyn IntrospectionCache>>,
}

/// Builder for [IntrospectionConfig]
Expand All @@ -26,6 +32,8 @@ impl IntrospectionStateBuilder {
Self {
authority: authority.to_string(),
authentication: None,
#[cfg(feature = "introspection_cache")]
cache: None,
}
}

Expand All @@ -48,6 +56,17 @@ impl IntrospectionStateBuilder {
self
}

/// Set the [IntrospectionCache] to use for caching introspection responses.
#[cfg(feature = "introspection_cache")]
pub fn with_introspection_cache(
&mut self,
cache: impl IntrospectionCache + 'static,
) -> &mut IntrospectionStateBuilder {
self.cache = Some(Box::new(cache));

self
}

pub async fn build(&mut self) -> Result<IntrospectionState, IntrospectionStateBuilderError> {
if self.authentication.is_none() {
return Err(IntrospectionStateBuilderError::NoAuthSchema);
Expand All @@ -67,11 +86,13 @@ impl IntrospectionStateBuilder {
}

Ok(IntrospectionState {
config: IntrospectionConfig {
config: Arc::new(IntrospectionConfig {
authority: self.authority.clone(),
introspection_uri: introspection_uri.unwrap(),
authentication: self.authentication.as_ref().unwrap().clone(),
},
#[cfg(feature = "introspection_cache")]
cache: self.cache.take(),
}),
})
}
}
Loading
Loading