mirror of
https://github.com/TECHNOFAB11/jwt-authorizer.git
synced 2025-12-10 23:20:05 +01:00
chore: fmt
This commit is contained in:
parent
ec02b70a99
commit
d8fb138d46
8 changed files with 86 additions and 78 deletions
|
|
@ -28,11 +28,13 @@ async fn main() {
|
|||
|
||||
// First let's create an authorizer builder from a JWKS Endpoint
|
||||
// User is a struct deserializable from JWT claims representing the authorized user
|
||||
let jwt_auth: JwtAuthorizer<User> = JwtAuthorizer::
|
||||
from_jwks_url("http://localhost:3000/oidc/jwks")
|
||||
// .no_refresh()
|
||||
.refresh(Refresh {strategy: RefreshStrategy::Interval, ..Default::default()})
|
||||
.check(claim_checker);
|
||||
let jwt_auth: JwtAuthorizer<User> = JwtAuthorizer::from_jwks_url("http://localhost:3000/oidc/jwks")
|
||||
// .no_refresh()
|
||||
.refresh(Refresh {
|
||||
strategy: RefreshStrategy::Interval,
|
||||
..Default::default()
|
||||
})
|
||||
.check(claim_checker);
|
||||
|
||||
let oidc = Router::new()
|
||||
.route("/authorize", post(oidc_provider::authorize))
|
||||
|
|
|
|||
|
|
@ -46,9 +46,7 @@ struct JwkSet {
|
|||
pub async fn jwks() -> Json<Value> {
|
||||
// let mut ksmap = serde_json::Map::new();
|
||||
|
||||
let mut kset = JwkSet {
|
||||
keys: Vec::<Jwk>::new(),
|
||||
};
|
||||
let mut kset = JwkSet { keys: Vec::<Jwk>::new() };
|
||||
|
||||
let keypair = RsaKeyPair::from_pem(include_bytes!("../../../config/jwtRS256.key")).unwrap();
|
||||
let mut pk = keypair.to_jwk_public_key();
|
||||
|
|
@ -64,19 +62,14 @@ pub async fn jwks() -> Json<Value> {
|
|||
pk.set_key_use("sig");
|
||||
kset.keys.push(pk);
|
||||
|
||||
let keypair =
|
||||
EcKeyPair::from_pem(include_bytes!("../../../config/ec256-private.pem"), Some(EcCurve::P256)).unwrap();
|
||||
let keypair = EcKeyPair::from_pem(include_bytes!("../../../config/ec256-private.pem"), Some(EcCurve::P256)).unwrap();
|
||||
let mut pk = keypair.to_jwk_public_key();
|
||||
pk.set_key_id("key-ec");
|
||||
pk.set_algorithm("ES256");
|
||||
pk.set_key_use("sig");
|
||||
kset.keys.push(pk);
|
||||
|
||||
let keypair = EcKeyPair::from_pem(
|
||||
include_bytes!("../../../config/private_ecdsa_key.pem"),
|
||||
Some(EcCurve::P256),
|
||||
)
|
||||
.unwrap();
|
||||
let keypair = EcKeyPair::from_pem(include_bytes!("../../../config/private_ecdsa_key.pem"), Some(EcCurve::P256)).unwrap();
|
||||
let mut pk = keypair.to_jwk_public_key();
|
||||
pk.set_key_id("ec01");
|
||||
pk.set_algorithm("ES256");
|
||||
|
|
@ -225,8 +218,8 @@ where
|
|||
let TypedHeader(Authorization(bearer)) = TypedHeader::<Authorization<Bearer>>::from_request_parts(parts, state)
|
||||
.await
|
||||
.map_err(|_| AuthError::InvalidToken)?;
|
||||
let token_data = decode::<Claims>(bearer.token(), &KEYS.decoding, &Validation::default())
|
||||
.map_err(|_| AuthError::InvalidToken)?;
|
||||
let token_data =
|
||||
decode::<Claims>(bearer.token(), &KEYS.decoding, &Validation::default()).map_err(|_| AuthError::InvalidToken)?;
|
||||
|
||||
Ok(token_data.claims)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,7 +5,8 @@ use serde::de::DeserializeOwned;
|
|||
|
||||
use crate::{
|
||||
error::{AuthError, InitError},
|
||||
jwks::{key_store_manager::KeyStoreManager, KeySource}, Refresh,
|
||||
jwks::{key_store_manager::KeyStoreManager, KeySource},
|
||||
Refresh,
|
||||
};
|
||||
|
||||
pub trait ClaimsChecker<C> {
|
||||
|
|
@ -66,7 +67,10 @@ where
|
|||
})
|
||||
}
|
||||
|
||||
pub(crate) fn from(key_source_type: &KeySourceType, claims_checker: Option<FnClaimsChecker<C>>) -> Result<Authorizer<C>, InitError> {
|
||||
pub(crate) fn from(
|
||||
key_source_type: &KeySourceType,
|
||||
claims_checker: Option<FnClaimsChecker<C>>,
|
||||
) -> Result<Authorizer<C>, InitError> {
|
||||
let key = match key_source_type {
|
||||
KeySourceType::RSA(path) => DecodingKey::from_rsa_pem(&read_data(path.as_str())?)?,
|
||||
KeySourceType::EC(path) => DecodingKey::from_ec_der(&read_data(path.as_str())?),
|
||||
|
|
@ -81,7 +85,11 @@ where
|
|||
})
|
||||
}
|
||||
|
||||
pub(crate) fn from_jwks_url(url: &str, claims_checker: Option<FnClaimsChecker<C>>, refresh: Refresh) -> Result<Authorizer<C>, InitError> {
|
||||
pub(crate) fn from_jwks_url(
|
||||
url: &str,
|
||||
claims_checker: Option<FnClaimsChecker<C>>,
|
||||
refresh: Refresh,
|
||||
) -> Result<Authorizer<C>, InitError> {
|
||||
let key_store_manager = KeyStoreManager::new(url, refresh);
|
||||
Ok(Authorizer {
|
||||
key_source: KeySource::KeyStoreSource(key_store_manager),
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
use axum::{
|
||||
body::{self, BoxBody, Empty},
|
||||
http::StatusCode,
|
||||
response::{IntoResponse, Response}, body::{self, Empty, BoxBody},
|
||||
response::{IntoResponse, Response},
|
||||
};
|
||||
use http::header;
|
||||
use jsonwebtoken::Algorithm;
|
||||
|
|
@ -47,7 +48,7 @@ pub enum AuthError {
|
|||
InvalidClaims(),
|
||||
}
|
||||
|
||||
fn response_wwwauth(status: StatusCode, bearer: &str) -> Response<BoxBody> {
|
||||
fn response_wwwauth(status: StatusCode, bearer: &str) -> Response<BoxBody> {
|
||||
let mut res = Response::new(body::boxed(Empty::new()));
|
||||
*res.status_mut() = status;
|
||||
let h = if bearer.is_empty() {
|
||||
|
|
@ -60,7 +61,7 @@ fn response_wwwauth(status: StatusCode, bearer: &str) -> Response<BoxBody> {
|
|||
res
|
||||
}
|
||||
|
||||
fn response_500() -> Response<BoxBody> {
|
||||
fn response_500() -> Response<BoxBody> {
|
||||
let mut res = Response::new(body::boxed(Empty::new()));
|
||||
*res.status_mut() = StatusCode::INTERNAL_SERVER_ERROR;
|
||||
|
||||
|
|
@ -71,38 +72,44 @@ fn response_500() -> Response<BoxBody> {
|
|||
impl IntoResponse for AuthError {
|
||||
fn into_response(self) -> Response {
|
||||
let resp = match self {
|
||||
AuthError::JwksRefreshError(err) => {
|
||||
AuthError::JwksRefreshError(err) => {
|
||||
tracing::error!("AuthErrors::JwksRefreshError: {}", err);
|
||||
response_500()
|
||||
},
|
||||
AuthError::InvalidKey(err) => {
|
||||
tracing::error!("AuthErrors::InvalidKey: {}", err);
|
||||
}
|
||||
AuthError::InvalidKey(err) => {
|
||||
tracing::error!("AuthErrors::InvalidKey: {}", err);
|
||||
response_500()
|
||||
},
|
||||
}
|
||||
AuthError::JwksSerialisationError(err) => {
|
||||
tracing::error!("AuthErrors::JwksSerialisationError: {}", err);
|
||||
tracing::error!("AuthErrors::JwksSerialisationError: {}", err);
|
||||
response_500()
|
||||
},
|
||||
}
|
||||
AuthError::InvalidKeyAlg(err) => {
|
||||
debug!("AuthErrors::InvalidKeyAlg: {:?}", err);
|
||||
response_wwwauth(StatusCode::UNAUTHORIZED, "error=\"invalid_token\", error_description=\"invalid key algorithm\"")
|
||||
},
|
||||
response_wwwauth(
|
||||
StatusCode::UNAUTHORIZED,
|
||||
"error=\"invalid_token\", error_description=\"invalid key algorithm\"",
|
||||
)
|
||||
}
|
||||
AuthError::InvalidKid(err) => {
|
||||
debug!("AuthErrors::InvalidKid: {}", err);
|
||||
response_wwwauth(StatusCode::UNAUTHORIZED, "error=\"invalid_token\", error_description=\"invalid kid\"")
|
||||
},
|
||||
response_wwwauth(
|
||||
StatusCode::UNAUTHORIZED,
|
||||
"error=\"invalid_token\", error_description=\"invalid kid\"",
|
||||
)
|
||||
}
|
||||
AuthError::InvalidToken(err) => {
|
||||
debug!("AuthErrors::InvalidToken: {}", err);
|
||||
response_wwwauth(StatusCode::UNAUTHORIZED, "error=\"invalid_token\"")
|
||||
},
|
||||
}
|
||||
AuthError::MissingToken() => {
|
||||
debug!("AuthErrors::MissingToken");
|
||||
response_wwwauth(StatusCode::UNAUTHORIZED, "")
|
||||
},
|
||||
}
|
||||
AuthError::InvalidClaims() => {
|
||||
debug!("AuthErrors::InvalidClaims");
|
||||
response_wwwauth(StatusCode::FORBIDDEN, "error=\"insufficient_scope\"")
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
resp
|
||||
|
|
|
|||
|
|
@ -12,7 +12,6 @@ use crate::error::AuthError;
|
|||
|
||||
#[derive(Clone, Copy)]
|
||||
pub enum RefreshStrategy {
|
||||
|
||||
/// refresh periodicaly
|
||||
Interval,
|
||||
|
||||
|
|
@ -37,7 +36,7 @@ pub struct Refresh {
|
|||
|
||||
impl Default for Refresh {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
Self {
|
||||
strategy: RefreshStrategy::KeyNotFound,
|
||||
refresh_interval: Duration::from_secs(600),
|
||||
minimal_refresh_interval: Duration::from_secs(30),
|
||||
|
|
@ -81,17 +80,15 @@ impl KeyStoreManager {
|
|||
let mut ks_gard = kstore.lock().await;
|
||||
let key = match self.refresh.strategy {
|
||||
RefreshStrategy::Interval => {
|
||||
if ks_gard.should_refresh(self.refresh.refresh_interval) && ks_gard.can_refresh(self.refresh.minimal_refresh_interval, self.refresh.retry_interval) {
|
||||
if ks_gard.should_refresh(self.refresh.refresh_interval)
|
||||
&& ks_gard.can_refresh(self.refresh.minimal_refresh_interval, self.refresh.retry_interval)
|
||||
{
|
||||
ks_gard.refresh(&self.key_url, &[]).await?;
|
||||
}
|
||||
if let Some(ref kid) = header.kid {
|
||||
ks_gard
|
||||
.find_kid(kid)
|
||||
.ok_or_else(|| AuthError::InvalidKid(kid.to_owned()))?
|
||||
ks_gard.find_kid(kid).ok_or_else(|| AuthError::InvalidKid(kid.to_owned()))?
|
||||
} else {
|
||||
ks_gard
|
||||
.find_alg(&header.alg)
|
||||
.ok_or(AuthError::InvalidKeyAlg(header.alg))?
|
||||
ks_gard.find_alg(&header.alg).ok_or(AuthError::InvalidKeyAlg(header.alg))?
|
||||
}
|
||||
}
|
||||
RefreshStrategy::KeyNotFound => {
|
||||
|
|
@ -101,11 +98,8 @@ impl KeyStoreManager {
|
|||
jwk
|
||||
} else if ks_gard.can_refresh(self.refresh.minimal_refresh_interval, self.refresh.retry_interval) {
|
||||
ks_gard.refresh(&self.key_url, &[("kid", kid)]).await?;
|
||||
ks_gard
|
||||
.find_kid(kid)
|
||||
.ok_or_else(|| AuthError::InvalidKid(kid.to_owned()))?
|
||||
ks_gard.find_kid(kid).ok_or_else(|| AuthError::InvalidKid(kid.to_owned()))?
|
||||
} else {
|
||||
|
||||
return Err(AuthError::InvalidKid(kid.to_owned()));
|
||||
}
|
||||
} else {
|
||||
|
|
@ -119,8 +113,7 @@ impl KeyStoreManager {
|
|||
&self.key_url,
|
||||
&[(
|
||||
"alg",
|
||||
&serde_json::to_string(&header.alg)
|
||||
.map_err(|_| AuthError::InvalidKeyAlg(header.alg))?,
|
||||
&serde_json::to_string(&header.alg).map_err(|_| AuthError::InvalidKeyAlg(header.alg))?,
|
||||
)],
|
||||
)
|
||||
.await?;
|
||||
|
|
@ -131,19 +124,15 @@ impl KeyStoreManager {
|
|||
return Err(AuthError::InvalidKeyAlg(header.alg));
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
RefreshStrategy::NoRefresh => {
|
||||
if ks_gard.load_time.is_none() {
|
||||
ks_gard.refresh(&self.key_url, &[]).await?;
|
||||
}
|
||||
if let Some(ref kid) = header.kid {
|
||||
ks_gard
|
||||
.find_kid(kid)
|
||||
.ok_or_else(|| AuthError::InvalidKid(kid.to_owned()))?
|
||||
ks_gard.find_kid(kid).ok_or_else(|| AuthError::InvalidKid(kid.to_owned()))?
|
||||
} else {
|
||||
ks_gard
|
||||
.find_alg(&header.alg)
|
||||
.ok_or(AuthError::InvalidKeyAlg(header.alg))?
|
||||
ks_gard.find_alg(&header.alg).ok_or(AuthError::InvalidKeyAlg(header.alg))?
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
@ -228,8 +217,8 @@ mod tests {
|
|||
Mock, MockServer, ResponseTemplate,
|
||||
};
|
||||
|
||||
use crate::{RefreshStrategy, Refresh};
|
||||
use crate::jwks::key_store_manager::{KeyStore, KeyStoreManager};
|
||||
use crate::{Refresh, RefreshStrategy};
|
||||
|
||||
#[test]
|
||||
fn keystore_should_refresh() {
|
||||
|
|
@ -252,7 +241,6 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn keystore_can_refresh() {
|
||||
|
||||
// FAIL, NO LOAD
|
||||
let ks = KeyStore {
|
||||
jwks: jsonwebtoken::jwk::JwkSet { keys: vec![] },
|
||||
|
|
@ -343,7 +331,11 @@ mod tests {
|
|||
|
||||
let ksm = KeyStoreManager::new(
|
||||
&mock_server.uri(),
|
||||
Refresh {strategy: RefreshStrategy::Interval, refresh_interval: Duration::from_secs(3000), ..Default::default()}
|
||||
Refresh {
|
||||
strategy: RefreshStrategy::Interval,
|
||||
refresh_interval: Duration::from_secs(3000),
|
||||
..Default::default()
|
||||
},
|
||||
);
|
||||
let r = ksm.get_key(&Header::new(Algorithm::EdDSA)).await;
|
||||
assert!(r.is_ok());
|
||||
|
|
@ -368,7 +360,11 @@ mod tests {
|
|||
|
||||
let mut ksm = KeyStoreManager::new(
|
||||
&mock_server.uri(),
|
||||
Refresh {strategy: RefreshStrategy::KeyNotFound, ..Default::default()});
|
||||
Refresh {
|
||||
strategy: RefreshStrategy::KeyNotFound,
|
||||
..Default::default()
|
||||
},
|
||||
);
|
||||
|
||||
// STEP 1: initial (lazy) reloading
|
||||
let r = ksm.get_key(&build_header("key-ed", Algorithm::EdDSA)).await;
|
||||
|
|
@ -440,7 +436,11 @@ mod tests {
|
|||
|
||||
let ksm = KeyStoreManager::new(
|
||||
&mock_server.uri(),
|
||||
Refresh {strategy: RefreshStrategy::NoRefresh, ..Default::default()});
|
||||
Refresh {
|
||||
strategy: RefreshStrategy::NoRefresh,
|
||||
..Default::default()
|
||||
},
|
||||
);
|
||||
|
||||
// STEP 1: initial (lazy) reloading
|
||||
let r = ksm.get_key(&build_header("key-ed", Algorithm::EdDSA)).await;
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
use axum::body::BoxBody;
|
||||
use axum::http::Request;
|
||||
use axum::response::{Response, IntoResponse};
|
||||
use axum::response::{IntoResponse, Response};
|
||||
use futures_core::ready;
|
||||
use futures_util::future::BoxFuture;
|
||||
use headers::authorization::Bearer;
|
||||
|
|
@ -14,10 +14,10 @@ use std::task::{Context, Poll};
|
|||
use tower_layer::Layer;
|
||||
use tower_service::Service;
|
||||
|
||||
use crate::{AuthError, RefreshStrategy};
|
||||
use crate::authorizer::{Authorizer, FnClaimsChecker, KeySourceType};
|
||||
use crate::error::InitError;
|
||||
use crate::jwks::key_store_manager::Refresh;
|
||||
use crate::{AuthError, RefreshStrategy};
|
||||
|
||||
/// Authorizer Layer builder
|
||||
///
|
||||
|
|
@ -118,11 +118,11 @@ where
|
|||
KeySourceType::RSA(_) | KeySourceType::EC(_) | KeySourceType::ED(_) | KeySourceType::Secret(_) => {
|
||||
Arc::new(Authorizer::from(key_source_type, self.claims_checker.clone())?)
|
||||
}
|
||||
KeySourceType::Jwks(url) => {
|
||||
Arc::new(
|
||||
Authorizer::from_jwks_url(
|
||||
url.as_str(), self.claims_checker.clone(), self.refresh.unwrap_or_default())?)
|
||||
}
|
||||
KeySourceType::Jwks(url) => Arc::new(Authorizer::from_jwks_url(
|
||||
url.as_str(),
|
||||
self.claims_checker.clone(),
|
||||
self.refresh.unwrap_or_default(),
|
||||
)?),
|
||||
}
|
||||
} else {
|
||||
return Err(InitError::BuilderError(
|
||||
|
|
@ -162,16 +162,14 @@ where
|
|||
Box::pin(async move {
|
||||
if let Some(bearer) = bearer_o {
|
||||
match authorizer.check_auth(bearer.token()).await {
|
||||
Ok(token_data) => {
|
||||
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);
|
||||
|
||||
|
||||
Ok(request)
|
||||
},
|
||||
Err(err) => {
|
||||
Err(err.into_response())
|
||||
}
|
||||
Err(err) => Err(err.into_response()),
|
||||
}
|
||||
} else {
|
||||
Err(AuthError::MissingToken().into_response())
|
||||
|
|
|
|||
|
|
@ -6,8 +6,8 @@ use jsonwebtoken::TokenData;
|
|||
use serde::de::DeserializeOwned;
|
||||
|
||||
pub use self::error::AuthError;
|
||||
pub use layer::JwtAuthorizer;
|
||||
pub use jwks::key_store_manager::{Refresh, RefreshStrategy};
|
||||
pub use layer::JwtAuthorizer;
|
||||
|
||||
pub mod authorizer;
|
||||
pub mod error;
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
max_width=120
|
||||
max_width=125
|
||||
Loading…
Add table
Add a link
Reference in a new issue