mirror of
https://github.com/TECHNOFAB11/jwt-authorizer.git
synced 2025-12-12 16:10:06 +01:00
feat: Add support for tonic
Tonic and Axum are quite closely related; From a tower perspective the main difference is in the Error type in the body for their Response. This refactor the code a little bit and add conversions from AuthError to a tonic's Response such that the exact same code can be used by both Axum and tonic services Signed-off-by: Sjoerd Simons <sjoerd@collabora.com>
This commit is contained in:
parent
f45568a044
commit
5f3a08c4c7
6 changed files with 547 additions and 257 deletions
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "jwt-authorizer"
|
||||
description = "jwt authorizer middleware for axum"
|
||||
description = "jwt authorizer middleware for axum and tonic"
|
||||
version = "0.9.0"
|
||||
edition = "2021"
|
||||
authors = ["cduvray <c_duvray@proton.me>"]
|
||||
|
|
@ -27,6 +27,7 @@ tower-layer = "0.3"
|
|||
tower-service = "0.3"
|
||||
tracing = "0.1"
|
||||
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
|
||||
tonic = { version = "0.9.2", optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
hyper = { version = "0.14", features = ["full"] }
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
# jwt-authorizer
|
||||
|
||||
JWT authoriser Layer for Axum.
|
||||
JWT authoriser Layer for Axum and Tonic.
|
||||
|
||||
## Features
|
||||
|
||||
|
|
|
|||
|
|
@ -77,6 +77,53 @@ fn response_500() -> Response<BoxBody> {
|
|||
res
|
||||
}
|
||||
|
||||
#[cfg(feature = "tonic")]
|
||||
impl From<AuthError> for Response<tonic::body::BoxBody> {
|
||||
fn from(e: AuthError) -> Self {
|
||||
match e {
|
||||
AuthError::JwksRefreshError(err) => {
|
||||
tracing::error!("AuthErrors::JwksRefreshError: {}", err);
|
||||
tonic::Status::internal("")
|
||||
}
|
||||
AuthError::InvalidKey(err) => {
|
||||
tracing::error!("AuthErrors::InvalidKey: {}", err);
|
||||
tonic::Status::internal("")
|
||||
}
|
||||
AuthError::JwksSerialisationError(err) => {
|
||||
tracing::error!("AuthErrors::JwksSerialisationError: {}", err);
|
||||
tonic::Status::internal("")
|
||||
}
|
||||
AuthError::InvalidKeyAlg(err) => {
|
||||
debug!("AuthErrors::InvalidKeyAlg: {:?}", err);
|
||||
tonic::Status::unauthenticated("error=\"invalid_token\", error_description=\"invalid key algorithm\"")
|
||||
}
|
||||
AuthError::InvalidKid(err) => {
|
||||
debug!("AuthErrors::InvalidKid: {}", err);
|
||||
tonic::Status::unauthenticated("error=\"invalid_token\", error_description=\"invalid kid\"")
|
||||
}
|
||||
AuthError::InvalidToken(err) => {
|
||||
debug!("AuthErrors::InvalidToken: {}", err);
|
||||
tonic::Status::unauthenticated("error=\"invalid_token\"")
|
||||
}
|
||||
AuthError::MissingToken() => {
|
||||
debug!("AuthErrors::MissingToken");
|
||||
tonic::Status::unauthenticated("")
|
||||
}
|
||||
AuthError::InvalidClaims() => {
|
||||
debug!("AuthErrors::InvalidClaims");
|
||||
tonic::Status::unauthenticated("error=\"insufficient_scope\"")
|
||||
}
|
||||
}
|
||||
.to_http()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<AuthError> for Response {
|
||||
fn from(e: AuthError) -> Self {
|
||||
e.into_response()
|
||||
}
|
||||
}
|
||||
|
||||
/// (https://datatracker.ietf.org/doc/html/rfc6750#section-3.1)
|
||||
impl IntoResponse for AuthError {
|
||||
fn into_response(self) -> Response {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,4 @@
|
|||
use axum::body::BoxBody;
|
||||
use axum::http::Request;
|
||||
use axum::response::{IntoResponse, Response};
|
||||
use futures_core::ready;
|
||||
use futures_util::future::BoxFuture;
|
||||
use headers::authorization::Bearer;
|
||||
|
|
@ -193,8 +191,7 @@ where
|
|||
/// Trait for authorizing requests.
|
||||
pub trait AsyncAuthorizer<B> {
|
||||
type RequestBody;
|
||||
type ResponseBody;
|
||||
type Future: Future<Output = Result<Request<Self::RequestBody>, Response<Self::ResponseBody>>>;
|
||||
type Future: Future<Output = Result<Request<Self::RequestBody>, AuthError>>;
|
||||
|
||||
/// Authorize the request.
|
||||
///
|
||||
|
|
@ -208,8 +205,7 @@ where
|
|||
C: Clone + DeserializeOwned + Send + Sync + 'static,
|
||||
{
|
||||
type RequestBody = B;
|
||||
type ResponseBody = BoxBody;
|
||||
type Future = BoxFuture<'static, Result<Request<B>, Response<Self::ResponseBody>>>;
|
||||
type Future = BoxFuture<'static, Result<Request<B>, AuthError>>;
|
||||
|
||||
fn authorize(&self, mut request: Request<B>) -> Self::Future {
|
||||
let authorizer = self.auth.clone();
|
||||
|
|
@ -226,18 +222,15 @@ where
|
|||
};
|
||||
Box::pin(async move {
|
||||
if let Some(token) = token {
|
||||
match authorizer.check_auth(token.as_str()).await {
|
||||
Ok(token_data) => {
|
||||
// Set `token_data` as a request extension so it can be accessed by other
|
||||
// services down the stack.
|
||||
request.extensions_mut().insert(token_data);
|
||||
authorizer.check_auth(token.as_str()).await.map(|token_data| {
|
||||
// Set `token_data` as a request extension so it can be accessed by other
|
||||
// services down the stack.
|
||||
request.extensions_mut().insert(token_data);
|
||||
|
||||
Ok(request)
|
||||
}
|
||||
Err(err) => Err(err.into_response()),
|
||||
}
|
||||
request
|
||||
})
|
||||
} else {
|
||||
Err(AuthError::MissingToken().into_response())
|
||||
Err(AuthError::MissingToken())
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
@ -335,7 +328,8 @@ where
|
|||
impl<ReqBody, S, C> Service<Request<ReqBody>> for AsyncAuthorizationService<S, C>
|
||||
where
|
||||
ReqBody: Send + Sync + 'static,
|
||||
S: Service<Request<ReqBody>, Response = Response> + Clone,
|
||||
S: Service<Request<ReqBody>> + Clone,
|
||||
S::Response: From<AuthError>,
|
||||
C: Clone + DeserializeOwned + Send + Sync + 'static,
|
||||
{
|
||||
type Response = S::Response;
|
||||
|
|
@ -361,7 +355,7 @@ where
|
|||
/// Response future for [`AsyncAuthorizationService`].
|
||||
pub struct ResponseFuture<S, ReqBody, C>
|
||||
where
|
||||
S: Service<Request<ReqBody>, Response = Response>,
|
||||
S: Service<Request<ReqBody>>,
|
||||
ReqBody: Send + Sync + 'static,
|
||||
C: Clone + DeserializeOwned + Send + Sync + 'static,
|
||||
{
|
||||
|
|
@ -384,7 +378,8 @@ enum State<A, SFut> {
|
|||
|
||||
impl<S, ReqBody, C> Future for ResponseFuture<S, ReqBody, C>
|
||||
where
|
||||
S: Service<Request<ReqBody>, Response = Response>,
|
||||
S: Service<Request<ReqBody>>,
|
||||
S::Response: From<AuthError>,
|
||||
ReqBody: Send + Sync + 'static,
|
||||
C: Clone + DeserializeOwned + Send + Sync,
|
||||
{
|
||||
|
|
@ -404,7 +399,7 @@ where
|
|||
}
|
||||
Err(res) => {
|
||||
tracing::info!("err: {:?}", res);
|
||||
return Poll::Ready(Ok(res));
|
||||
return Poll::Ready(Ok(res.into()));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue