From e95677413b622ab107d941855615267868b82c41 Mon Sep 17 00:00:00 2001 From: Vladislav Manchev Date: Thu, 2 Jan 2025 00:08:49 +0200 Subject: [PATCH 1/2] - Upgrade axum to 0.8.1 - Upgrade to tonic 0.13.0 - Add OptionalFromRequestParts implementation - Update dependencies --- demo-server/Cargo.toml | 26 +++++++++--------- jwt-authorizer/Cargo.toml | 24 ++++++++-------- jwt-authorizer/src/error.rs | 2 +- jwt-authorizer/src/jwks/key_store_manager.rs | 4 +-- jwt-authorizer/src/lib.rs | 22 +++++++++++++-- jwt-authorizer/tests/tonic.rs | 29 +++++++------------- 6 files changed, 57 insertions(+), 50 deletions(-) diff --git a/demo-server/Cargo.toml b/demo-server/Cargo.toml index 5b5b198..7284790 100644 --- a/demo-server/Cargo.toml +++ b/demo-server/Cargo.toml @@ -6,18 +6,18 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -anyhow = "1.0.83" -axum = { version = "0.7.5" } -headers = "0.4" -josekit = "0.8.6" +anyhow = "1.0.95" +axum = { version = "0.8.1" } +headers = "0.4.0" +josekit = "0.10.1" jsonwebtoken = "9.3.0" -once_cell = "1.19.0" -reqwest = { version = "0.12.4", features = ["json"] } -serde = { version = "1.0", features = ["derive"] } -serde_json = "1.0" -thiserror = "1.0.60" -tokio = { version = "1.37.0", features = ["full"] } -tower-http = { version = "0.5.2", features = ["trace"] } -tracing = "0.1.40" -tracing-subscriber = { version = "0.3", features = ["env-filter"] } +once_cell = "1.20.2" +reqwest = { version = "0.12.12", features = ["json"] } +serde = { version = "1.0.217", features = ["derive"] } +serde_json = "1.0.134" +thiserror = "2.0.9" +tokio = { version = "1.42.0", features = ["full"] } +tower-http = { version = "0.6.2", features = ["trace"] } +tracing = "0.1.41" +tracing-subscriber = { version = "0.3.19", features = ["env-filter"] } jwt-authorizer = { path = "../jwt-authorizer" } diff --git a/jwt-authorizer/Cargo.toml b/jwt-authorizer/Cargo.toml index ba6421c..e77e911 100644 --- a/jwt-authorizer/Cargo.toml +++ b/jwt-authorizer/Cargo.toml @@ -10,34 +10,34 @@ repository = "https://github.com/cduvray/jwt-authorizer" keywords = ["jwt", "axum", "authorisation", "jwks"] [dependencies] -axum = { version = "0.7" } +axum = { version = "0.8" } chrono = { version = "0.4", optional = true } futures-util = "0.3" futures-core = "0.3" headers = "0.4" jsonwebtoken = "9.3" -http = "1.1" +http = "1.2" pin-project = "1.1" -reqwest = { version = "0.12.4", default-features = false, features = ["json"] } +reqwest = { version = "0.12", default-features = false, features = ["json"] } serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" -thiserror = "1.0" -tokio = { version = "1.37", features = ["full"] } -tower-http = { version = "0.5", features = ["trace", "auth"] } +thiserror = "2.0" +tokio = { version = "1.42", features = ["full"] } +tower-http = { version = "0.6", features = ["trace", "auth"] } tower-layer = "0.3" tower-service = "0.3" tracing = "0.1" -tonic = { version = "0.12", optional = true } +tonic = { git = "https://github.com/hyperium/tonic", optional = true } time = { version = "0.3", optional = true } -http-body-util = "0.1.1" +http-body-util = "0.1" [dev-dependencies] -hyper = { version = "1.3.1", features = ["full"] } -lazy_static = "1.4.0" +hyper = { version = "1.5", features = ["full"] } +lazy_static = "1.5" prost = "0.13" -tower = { version = "0.4.13", features = ["util", "buffer"] } +tower = { version = "0.5", features = ["util", "buffer"] } tracing-subscriber = { version = "0.3", features = ["env-filter"] } -wiremock = "0.6.1" +wiremock = "0.6" [features] default = ["default-tls", "chrono"] diff --git a/jwt-authorizer/src/error.rs b/jwt-authorizer/src/error.rs index 465eb31..3bca32d 100644 --- a/jwt-authorizer/src/error.rs +++ b/jwt-authorizer/src/error.rs @@ -85,7 +85,7 @@ fn response_500() -> Response { } #[cfg(feature = "tonic")] -impl From for Response { +impl From for Response { fn from(e: AuthError) -> Self { match e { AuthError::JwksRefreshError(err) => { diff --git a/jwt-authorizer/src/jwks/key_store_manager.rs b/jwt-authorizer/src/jwks/key_store_manager.rs index 86eba6a..70f551e 100644 --- a/jwt-authorizer/src/jwks/key_store_manager.rs +++ b/jwt-authorizer/src/jwks/key_store_manager.rs @@ -114,9 +114,7 @@ impl KeyStoreManager { )], ) .await?; - ks_gard - .find_alg(&header.alg) - .ok_or_else(|| AuthError::InvalidKeyAlg(header.alg))? + ks_gard.find_alg(&header.alg).ok_or(AuthError::InvalidKeyAlg(header.alg))? } else { return Err(AuthError::InvalidKeyAlg(header.alg)); } diff --git a/jwt-authorizer/src/lib.rs b/jwt-authorizer/src/lib.rs index 86878bc..52a1863 100644 --- a/jwt-authorizer/src/lib.rs +++ b/jwt-authorizer/src/lib.rs @@ -1,6 +1,9 @@ #![doc = include_str!("../docs/README.md")] -use axum::{async_trait, extract::FromRequestParts, http::request::Parts}; +use axum::{ + extract::{FromRequestParts, OptionalFromRequestParts}, + http::request::Parts, +}; use jsonwebtoken::TokenData; use serde::de::DeserializeOwned; @@ -24,7 +27,6 @@ pub mod validation; #[derive(Debug, Clone, Copy, Default)] pub struct JwtClaims(pub T); -#[async_trait] impl FromRequestParts for JwtClaims where T: DeserializeOwned + Send + Sync + Clone + 'static, @@ -40,3 +42,19 @@ where } } } + +impl OptionalFromRequestParts for JwtClaims +where + T: DeserializeOwned + Send + Sync + Clone + 'static, + S: Send + Sync, +{ + type Rejection = AuthError; + + async fn from_request_parts(parts: &mut Parts, _: &S) -> Result, Self::Rejection> { + if let Some(claims) = parts.extensions.get::>() { + Ok(Some(JwtClaims(claims.claims.clone()))) + } else { + Ok(None) + } + } +} diff --git a/jwt-authorizer/tests/tonic.rs b/jwt-authorizer/tests/tonic.rs index 158ca6d..3a56ebc 100644 --- a/jwt-authorizer/tests/tonic.rs +++ b/jwt-authorizer/tests/tonic.rs @@ -1,12 +1,11 @@ use std::{sync::Once, task::Poll}; -use axum::extract::Request; use futures_core::future::BoxFuture; use http::header::AUTHORIZATION; use jwt_authorizer::{layer::AuthorizationService, IntoLayer, JwtAuthorizer, Validation}; use serde::{Deserialize, Serialize}; use tonic::{server::NamedService, server::UnaryService, IntoRequest, Status}; -use tower::{buffer::Buffer, Service}; +use tower::Service; use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt}; @@ -50,8 +49,8 @@ struct GreeterServer { expected_sub: String, } -impl Service> for GreeterServer { - type Response = http::Response; +impl Service> for GreeterServer { + type Response = http::Response; type Error = std::convert::Infallible; type Future = BoxFuture<'static, Result>; @@ -59,7 +58,7 @@ impl Service> for GreeterServer { Poll::Ready(Ok(())) } - fn call(&mut self, req: http::Request) -> Self::Future { + fn call(&mut self, req: http::Request) -> Self::Future { let token = req.extensions().get::>().unwrap(); assert_eq!(token.claims.sub, self.expected_sub); match req.uri().path() { @@ -79,16 +78,11 @@ impl NamedService for GreeterServer { const NAME: &'static str = "hello"; } -async fn app( - jwt_auth: JwtAuthorizer, - expected_sub: String, -) -> AuthorizationService>, User> { +async fn app(jwt_auth: JwtAuthorizer, expected_sub: String) -> AuthorizationService { let layer = jwt_auth.build().await.unwrap().into_layer(); - tonic::transport::Server::builder() - .layer(layer) - .layer(tower::buffer::BufferLayer::new(1)) - .add_service(GreeterServer { expected_sub }) - .into_service() + let routes = tonic::service::Routes::new(GreeterServer { expected_sub }).prepare(); + + tower::ServiceBuilder::new().layer(layer).service(routes) } fn init_test() { @@ -108,11 +102,8 @@ async fn make_protected_request( message: &str, ) -> Result, Status> where - S: Service< - http::Request, - Response = http::Response, - Error = tower::BoxError, - > + Send + S: Service, Response = http::Response, Error = tower::BoxError> + + Send + Clone + 'static, S::Future: Send, From d5e8b86cee61ff0be3b97e48986c3794a800b7eb Mon Sep 17 00:00:00 2001 From: Vladislav Manchev Date: Sat, 29 Mar 2025 00:42:10 +0200 Subject: [PATCH 2/2] Switch to tonic 0.13, use Infallible for errors in tests --- jwt-authorizer/Cargo.toml | 2 +- jwt-authorizer/src/jwks/key_store_manager.rs | 4 ++-- jwt-authorizer/tests/tonic.rs | 7 +++++-- 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/jwt-authorizer/Cargo.toml b/jwt-authorizer/Cargo.toml index e77e911..2d99c08 100644 --- a/jwt-authorizer/Cargo.toml +++ b/jwt-authorizer/Cargo.toml @@ -27,7 +27,7 @@ tower-http = { version = "0.6", features = ["trace", "auth"] } tower-layer = "0.3" tower-service = "0.3" tracing = "0.1" -tonic = { git = "https://github.com/hyperium/tonic", optional = true } +tonic = { version = "0.13", optional = true } time = { version = "0.3", optional = true } http-body-util = "0.1" diff --git a/jwt-authorizer/src/jwks/key_store_manager.rs b/jwt-authorizer/src/jwks/key_store_manager.rs index 70f551e..d72a70c 100644 --- a/jwt-authorizer/src/jwks/key_store_manager.rs +++ b/jwt-authorizer/src/jwks/key_store_manager.rs @@ -30,9 +30,9 @@ pub struct Refresh { /// After the refresh interval the store will/can be refreshed. /// /// - RefreshStrategy::KeyNotFound - refresh will be performed only if a kid is not found in the store - /// (if no kid is in the token header the alg is looked up) + /// (if no kid is in the token header the alg is looked up) /// - RefreshStrategy::Interval - refresh will be performed each time the refresh interval has elapsed - /// (before checking a new token -> lazy behaviour) + /// (before checking a new token -> lazy behaviour) pub refresh_interval: Duration, /// don't refresh before (after an error or jwks is unawailable) /// (we let a little bit of time to the jwks endpoint to recover) diff --git a/jwt-authorizer/tests/tonic.rs b/jwt-authorizer/tests/tonic.rs index 3a56ebc..c8796c8 100644 --- a/jwt-authorizer/tests/tonic.rs +++ b/jwt-authorizer/tests/tonic.rs @@ -102,8 +102,11 @@ async fn make_protected_request( message: &str, ) -> Result, Status> where - S: Service, Response = http::Response, Error = tower::BoxError> - + Send + S: Service< + http::Request, + Response = http::Response, + Error = std::convert::Infallible, + > + Send + Clone + 'static, S::Future: Send,