mirror of
https://github.com/TECHNOFAB11/jwt-authorizer.git
synced 2025-12-12 16:10:06 +01:00
feat: optional claim extraction (fixes #12)
- error 401 rather than INTERNAL_SERVER_ERROR, when no claims exist (no layer in front of the handler) - do not log error - tests
This commit is contained in:
parent
27cce24372
commit
940acb17a1
3 changed files with 33 additions and 6 deletions
|
|
@ -55,6 +55,10 @@ pub enum AuthError {
|
||||||
|
|
||||||
#[error("Invalid Claim")]
|
#[error("Invalid Claim")]
|
||||||
InvalidClaims(),
|
InvalidClaims(),
|
||||||
|
|
||||||
|
/// Used when a claim extractor is used and no authorization layer is in front the handler
|
||||||
|
#[error("No Authorizer Layer")]
|
||||||
|
NoAuthorizerLayer(),
|
||||||
}
|
}
|
||||||
|
|
||||||
fn response_wwwauth(status: StatusCode, bearer: &str) -> Response<BoxBody> {
|
fn response_wwwauth(status: StatusCode, bearer: &str) -> Response<BoxBody> {
|
||||||
|
|
@ -113,6 +117,10 @@ impl From<AuthError> for Response<tonic::body::BoxBody> {
|
||||||
debug!("AuthErrors::InvalidClaims");
|
debug!("AuthErrors::InvalidClaims");
|
||||||
tonic::Status::unauthenticated("error=\"insufficient_scope\"")
|
tonic::Status::unauthenticated("error=\"insufficient_scope\"")
|
||||||
}
|
}
|
||||||
|
AuthError::NoAuthorizerLayer() => {
|
||||||
|
debug!("AuthErrors::NoAuthorizerLayer");
|
||||||
|
tonic::Status::unauthenticated("error=\"no_authorizer_layer\"")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.to_http()
|
.to_http()
|
||||||
}
|
}
|
||||||
|
|
@ -166,6 +174,11 @@ impl IntoResponse for AuthError {
|
||||||
debug!("AuthErrors::InvalidClaims");
|
debug!("AuthErrors::InvalidClaims");
|
||||||
response_wwwauth(StatusCode::FORBIDDEN, "error=\"insufficient_scope\"")
|
response_wwwauth(StatusCode::FORBIDDEN, "error=\"insufficient_scope\"")
|
||||||
}
|
}
|
||||||
|
AuthError::NoAuthorizerLayer() => {
|
||||||
|
debug!("AuthErrors::NoAuthorizerLayer");
|
||||||
|
// TODO: should it be a standard error?
|
||||||
|
response_wwwauth(StatusCode::UNAUTHORIZED, "error=\"no_authorizer_layer\"")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
#![doc = include_str!("../docs/README.md")]
|
#![doc = include_str!("../docs/README.md")]
|
||||||
|
|
||||||
use axum::{async_trait, extract::FromRequestParts, http::request::Parts};
|
use axum::{async_trait, extract::FromRequestParts, http::request::Parts};
|
||||||
use http::StatusCode;
|
|
||||||
use jsonwebtoken::TokenData;
|
use jsonwebtoken::TokenData;
|
||||||
use serde::de::DeserializeOwned;
|
use serde::de::DeserializeOwned;
|
||||||
|
|
||||||
|
|
@ -29,14 +28,13 @@ where
|
||||||
T: DeserializeOwned + Send + Sync + Clone + 'static,
|
T: DeserializeOwned + Send + Sync + Clone + 'static,
|
||||||
S: Send + Sync,
|
S: Send + Sync,
|
||||||
{
|
{
|
||||||
type Rejection = StatusCode;
|
type Rejection = AuthError;
|
||||||
|
|
||||||
async fn from_request_parts(parts: &mut Parts, _: &S) -> Result<Self, Self::Rejection> {
|
async fn from_request_parts(parts: &mut Parts, _: &S) -> Result<Self, Self::Rejection> {
|
||||||
if let Some(claims) = parts.extensions.get::<TokenData<T>>() {
|
if let Some(claims) = parts.extensions.get::<TokenData<T>>() {
|
||||||
Ok(JwtClaims(claims.claims.clone()))
|
Ok(JwtClaims(claims.claims.clone()))
|
||||||
} else {
|
} else {
|
||||||
tracing::error!("JwtClaims extractor must be behind a jwt-authoriser layer!");
|
Err(AuthError::NoAuthorizerLayer())
|
||||||
Err(StatusCode::INTERNAL_SERVER_ERROR)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -146,7 +146,7 @@ mod tests {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn extract_from_public_500() {
|
async fn extract_from_public_401() {
|
||||||
let app = Router::new().route(
|
let app = Router::new().route(
|
||||||
"/public",
|
"/public",
|
||||||
get(|JwtClaims(user): JwtClaims<User>| async move { format!("hello: {}", user.sub) }),
|
get(|JwtClaims(user): JwtClaims<User>| async move { format!("hello: {}", user.sub) }),
|
||||||
|
|
@ -156,7 +156,23 @@ mod tests {
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
assert_eq!(response.status(), StatusCode::INTERNAL_SERVER_ERROR);
|
assert_eq!(response.status(), StatusCode::UNAUTHORIZED);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn extract_from_public_optional() {
|
||||||
|
let app = Router::new().route(
|
||||||
|
"/public",
|
||||||
|
get(|user: Option<JwtClaims<User>>| async move { format!("option: {}", user.is_none()) }),
|
||||||
|
);
|
||||||
|
let response = app
|
||||||
|
.oneshot(Request::builder().uri("/public").body(Body::empty()).unwrap())
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
assert_eq!(response.status(), StatusCode::OK);
|
||||||
|
let body = hyper::body::to_bytes(response.into_body()).await.unwrap();
|
||||||
|
assert_eq!(&body[..], b"option: true");
|
||||||
}
|
}
|
||||||
|
|
||||||
// --------------------
|
// --------------------
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue