mirror of
https://github.com/TECHNOFAB11/jwt-authorizer.git
synced 2025-12-11 23:50:07 +01:00
refactor: Authorizer::build
This commit is contained in:
parent
6ff5d88ae9
commit
b189caaab8
5 changed files with 102 additions and 81 deletions
9
.editorconfig
Normal file
9
.editorconfig
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
root = true
|
||||||
|
|
||||||
|
[*]
|
||||||
|
end_of_line = lf
|
||||||
|
charset = utf-8
|
||||||
|
trim_trailing_whitespace = true
|
||||||
|
insert_final_newline = true
|
||||||
|
indent_style = space
|
||||||
|
indent_size = 4
|
||||||
|
|
@ -15,11 +15,13 @@ JWT authoriser Layer for Axum.
|
||||||
## Usage Example
|
## Usage Example
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
use jwt_authorizer::{AuthError, JwtAuthorizer, JwtClaims};
|
# use jwt_authorizer::{AuthError, JwtAuthorizer, JwtClaims};
|
||||||
use axum::{routing::get, Router};
|
# use axum::{routing::get, Router};
|
||||||
use serde::Deserialize;
|
# use serde::Deserialize;
|
||||||
|
|
||||||
// Authorized entity, struct deserializable from JWT claims
|
# async {
|
||||||
|
|
||||||
|
// struct representing the authorized caller, deserializable from JWT claims
|
||||||
#[derive(Debug, Deserialize, Clone)]
|
#[derive(Debug, Deserialize, Clone)]
|
||||||
struct User {
|
struct User {
|
||||||
sub: String,
|
sub: String,
|
||||||
|
|
@ -39,10 +41,9 @@ JWT authoriser Layer for Axum.
|
||||||
Ok(format!("Welcome: {}", user.sub))
|
Ok(format!("Welcome: {}", user.sub))
|
||||||
}
|
}
|
||||||
|
|
||||||
# async {
|
|
||||||
axum::Server::bind(&"0.0.0.0:3000".parse().unwrap())
|
axum::Server::bind(&"0.0.0.0:3000".parse().unwrap())
|
||||||
.serve(app.into_make_service()).await.expect("server failed");
|
.serve(app.into_make_service()).await.expect("server failed");
|
||||||
# };
|
# };
|
||||||
```
|
```
|
||||||
|
|
||||||
## ClaimsChecker
|
## ClaimsChecker
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ use serde::de::DeserializeOwned;
|
||||||
use crate::{
|
use crate::{
|
||||||
error::{AuthError, InitError},
|
error::{AuthError, InitError},
|
||||||
jwks::{key_store_manager::KeyStoreManager, KeySource},
|
jwks::{key_store_manager::KeyStoreManager, KeySource},
|
||||||
Refresh,
|
oidc, Refresh,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub trait ClaimsChecker<C> {
|
pub trait ClaimsChecker<C> {
|
||||||
|
|
@ -51,6 +51,7 @@ pub enum KeySourceType {
|
||||||
ED(String),
|
ED(String),
|
||||||
Secret(&'static str),
|
Secret(&'static str),
|
||||||
Jwks(String),
|
Jwks(String),
|
||||||
|
JwksString(String), // TODO: expose JwksString in JwtAuthorizer or remove it
|
||||||
Discovery(String),
|
Discovery(String),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -58,44 +59,65 @@ impl<C> Authorizer<C>
|
||||||
where
|
where
|
||||||
C: DeserializeOwned + Clone + Send + Sync,
|
C: DeserializeOwned + Clone + Send + Sync,
|
||||||
{
|
{
|
||||||
// TODO: expose it in JwtAuthorizer
|
pub(crate) async fn build(
|
||||||
pub fn from_jwks(jwks: &str, claims_checker: Option<FnClaimsChecker<C>>) -> Result<Authorizer<C>, AuthError> {
|
|
||||||
let set: JwkSet = serde_json::from_str(jwks)?;
|
|
||||||
let k = DecodingKey::from_jwk(&set.keys[0])?;
|
|
||||||
|
|
||||||
Ok(Authorizer {
|
|
||||||
key_source: KeySource::DecodingKeySource(k),
|
|
||||||
claims_checker,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn from(
|
|
||||||
key_source_type: &KeySourceType,
|
key_source_type: &KeySourceType,
|
||||||
claims_checker: Option<FnClaimsChecker<C>>,
|
claims_checker: Option<FnClaimsChecker<C>>,
|
||||||
|
refresh: Option<Refresh>,
|
||||||
) -> Result<Authorizer<C>, InitError> {
|
) -> Result<Authorizer<C>, InitError> {
|
||||||
let key = match key_source_type {
|
Ok(match key_source_type {
|
||||||
KeySourceType::RSA(path) => DecodingKey::from_rsa_pem(&read_data(path.as_str())?)?,
|
KeySourceType::RSA(path) => {
|
||||||
KeySourceType::EC(path) => DecodingKey::from_ec_der(&read_data(path.as_str())?),
|
let key = DecodingKey::from_rsa_pem(&read_data(path.as_str())?)?;
|
||||||
KeySourceType::ED(path) => DecodingKey::from_ed_der(&read_data(path.as_str())?),
|
Authorizer {
|
||||||
KeySourceType::Secret(secret) => DecodingKey::from_secret(secret.as_bytes()),
|
|
||||||
_ => panic!("bug: use from_jwks_url() or from_oidc() to initialise Authorizer"), // should never hapen
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(Authorizer {
|
|
||||||
key_source: KeySource::DecodingKeySource(key),
|
key_source: KeySource::DecodingKeySource(key),
|
||||||
claims_checker,
|
claims_checker,
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
pub(crate) fn from_jwks_url(
|
KeySourceType::EC(path) => {
|
||||||
url: &str,
|
let key = DecodingKey::from_ec_der(&read_data(path.as_str())?);
|
||||||
claims_checker: Option<FnClaimsChecker<C>>,
|
Authorizer {
|
||||||
refresh: Refresh,
|
key_source: KeySource::DecodingKeySource(key),
|
||||||
) -> Result<Authorizer<C>, InitError> {
|
claims_checker,
|
||||||
let key_store_manager = KeyStoreManager::new(url, refresh);
|
}
|
||||||
Ok(Authorizer {
|
}
|
||||||
|
KeySourceType::ED(path) => {
|
||||||
|
let key = DecodingKey::from_ed_der(&read_data(path.as_str())?);
|
||||||
|
Authorizer {
|
||||||
|
key_source: KeySource::DecodingKeySource(key),
|
||||||
|
claims_checker,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
KeySourceType::Secret(secret) => {
|
||||||
|
let key = DecodingKey::from_secret(secret.as_bytes());
|
||||||
|
Authorizer {
|
||||||
|
key_source: KeySource::DecodingKeySource(key),
|
||||||
|
claims_checker,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
KeySourceType::JwksString(jwks_str) => {
|
||||||
|
// TODO: expose it in JwtAuthorizer or remove
|
||||||
|
let set: JwkSet = serde_json::from_str(jwks_str)?;
|
||||||
|
// TODO: replace [0] by kid/alg search
|
||||||
|
let k = DecodingKey::from_jwk(&set.keys[0])?;
|
||||||
|
Authorizer {
|
||||||
|
key_source: KeySource::DecodingKeySource(k),
|
||||||
|
claims_checker,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
KeySourceType::Jwks(url) => {
|
||||||
|
let key_store_manager = KeyStoreManager::new(url, refresh.unwrap_or_default());
|
||||||
|
Authorizer {
|
||||||
key_source: KeySource::KeyStoreSource(key_store_manager),
|
key_source: KeySource::KeyStoreSource(key_store_manager),
|
||||||
claims_checker,
|
claims_checker,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
KeySourceType::Discovery(issuer_url) => {
|
||||||
|
let jwks_url = oidc::discover_jwks(issuer_url).await?;
|
||||||
|
let key_store_manager = KeyStoreManager::new(&jwks_url, refresh.unwrap_or_default());
|
||||||
|
Authorizer {
|
||||||
|
key_source: KeySource::KeyStoreSource(key_store_manager),
|
||||||
|
claims_checker,
|
||||||
|
}
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -126,7 +148,9 @@ mod tests {
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn from_secret() {
|
async fn from_secret() {
|
||||||
let h = Header::new(Algorithm::HS256);
|
let h = Header::new(Algorithm::HS256);
|
||||||
let a = Authorizer::<Value>::from(&KeySourceType::Secret("xxxxxx"), None).unwrap();
|
let a = Authorizer::<Value>::build(&KeySourceType::Secret("xxxxxx"), None, None)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
let k = a.key_source.get_key(h);
|
let k = a.key_source.get_key(h);
|
||||||
assert!(k.await.is_ok());
|
assert!(k.await.is_ok());
|
||||||
}
|
}
|
||||||
|
|
@ -143,29 +167,37 @@ mod tests {
|
||||||
"e": "AQAB"
|
"e": "AQAB"
|
||||||
}]}
|
}]}
|
||||||
"#;
|
"#;
|
||||||
let a = Authorizer::<Value>::from_jwks(jwks, None).unwrap();
|
let a = Authorizer::<Value>::build(&KeySourceType::JwksString(jwks.to_owned()), None, None)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
let k = a.key_source.get_key(Header::new(Algorithm::RS256));
|
let k = a.key_source.get_key(Header::new(Algorithm::RS256));
|
||||||
assert!(k.await.is_ok());
|
assert!(k.await.is_ok());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn from_file() {
|
async fn from_file() {
|
||||||
let a = Authorizer::<Value>::from(&KeySourceType::RSA("../config/jwtRS256.key.pub".to_owned()), None).unwrap();
|
let a = Authorizer::<Value>::build(&KeySourceType::RSA("../config/jwtRS256.key.pub".to_owned()), None, None)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
let k = a.key_source.get_key(Header::new(Algorithm::RS256));
|
let k = a.key_source.get_key(Header::new(Algorithm::RS256));
|
||||||
assert!(k.await.is_ok());
|
assert!(k.await.is_ok());
|
||||||
|
|
||||||
let a = Authorizer::<Value>::from(&KeySourceType::EC("../config/ec256-public.pem".to_owned()), None).unwrap();
|
let a = Authorizer::<Value>::build(&KeySourceType::EC("../config/ec256-public.pem".to_owned()), None, None)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
let k = a.key_source.get_key(Header::new(Algorithm::ES256));
|
let k = a.key_source.get_key(Header::new(Algorithm::ES256));
|
||||||
assert!(k.await.is_ok());
|
assert!(k.await.is_ok());
|
||||||
|
|
||||||
let a = Authorizer::<Value>::from(&KeySourceType::ED("../config/ed25519-public.pem".to_owned()), None).unwrap();
|
let a = Authorizer::<Value>::build(&KeySourceType::ED("../config/ed25519-public.pem".to_owned()), None, None)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
let k = a.key_source.get_key(Header::new(Algorithm::EdDSA));
|
let k = a.key_source.get_key(Header::new(Algorithm::EdDSA));
|
||||||
assert!(k.await.is_ok());
|
assert!(k.await.is_ok());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn from_file_errors() {
|
async fn from_file_errors() {
|
||||||
let a = Authorizer::<Value>::from(&KeySourceType::RSA("./config/does-not-exist.pem".to_owned()), None);
|
let a = Authorizer::<Value>::build(&KeySourceType::RSA("./config/does-not-exist.pem".to_owned()), None, None).await;
|
||||||
println!("{:?}", a.as_ref().err());
|
println!("{:?}", a.as_ref().err());
|
||||||
assert!(a.is_err());
|
assert!(a.is_err());
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,9 @@ pub enum InitError {
|
||||||
|
|
||||||
#[error("Builder Error {0}")]
|
#[error("Builder Error {0}")]
|
||||||
DiscoveryError(String),
|
DiscoveryError(String),
|
||||||
|
|
||||||
|
#[error("Jwks Parsing Error {0}")]
|
||||||
|
JwksParsingError(#[from] serde_json::Error),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Error)]
|
#[derive(Debug, Error)]
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@ use tower_service::Service;
|
||||||
use crate::authorizer::{Authorizer, FnClaimsChecker, KeySourceType};
|
use crate::authorizer::{Authorizer, FnClaimsChecker, KeySourceType};
|
||||||
use crate::error::InitError;
|
use crate::error::InitError;
|
||||||
use crate::jwks::key_store_manager::Refresh;
|
use crate::jwks::key_store_manager::Refresh;
|
||||||
use crate::{oidc, AuthError, RefreshStrategy};
|
use crate::{AuthError, RefreshStrategy};
|
||||||
|
|
||||||
/// Authorizer Layer builder
|
/// Authorizer Layer builder
|
||||||
///
|
///
|
||||||
|
|
@ -27,7 +27,7 @@ pub struct JwtAuthorizer<C>
|
||||||
where
|
where
|
||||||
C: Clone + DeserializeOwned,
|
C: Clone + DeserializeOwned,
|
||||||
{
|
{
|
||||||
key_source_type: Option<KeySourceType>,
|
key_source_type: KeySourceType,
|
||||||
refresh: Option<Refresh>,
|
refresh: Option<Refresh>,
|
||||||
claims_checker: Option<FnClaimsChecker<C>>,
|
claims_checker: Option<FnClaimsChecker<C>>,
|
||||||
}
|
}
|
||||||
|
|
@ -40,7 +40,7 @@ where
|
||||||
/// Builds Authorizer Layer from a OpenId Connect discover metadata
|
/// Builds Authorizer Layer from a OpenId Connect discover metadata
|
||||||
pub fn from_oidc(issuer: &str) -> JwtAuthorizer<C> {
|
pub fn from_oidc(issuer: &str) -> JwtAuthorizer<C> {
|
||||||
JwtAuthorizer {
|
JwtAuthorizer {
|
||||||
key_source_type: Some(KeySourceType::Discovery(issuer.to_string())),
|
key_source_type: KeySourceType::Discovery(issuer.to_string()),
|
||||||
refresh: Default::default(),
|
refresh: Default::default(),
|
||||||
claims_checker: None,
|
claims_checker: None,
|
||||||
}
|
}
|
||||||
|
|
@ -49,7 +49,7 @@ where
|
||||||
/// Builds Authorizer Layer from a JWKS endpoint
|
/// Builds Authorizer Layer from a JWKS endpoint
|
||||||
pub fn from_jwks_url(url: &'static str) -> JwtAuthorizer<C> {
|
pub fn from_jwks_url(url: &'static str) -> JwtAuthorizer<C> {
|
||||||
JwtAuthorizer {
|
JwtAuthorizer {
|
||||||
key_source_type: Some(KeySourceType::Jwks(url.to_owned())),
|
key_source_type: KeySourceType::Jwks(url.to_owned()),
|
||||||
refresh: Default::default(),
|
refresh: Default::default(),
|
||||||
claims_checker: None,
|
claims_checker: None,
|
||||||
}
|
}
|
||||||
|
|
@ -58,7 +58,7 @@ where
|
||||||
/// Builds Authorizer Layer from a RSA PEM file
|
/// Builds Authorizer Layer from a RSA PEM file
|
||||||
pub fn from_rsa_pem(path: &'static str) -> JwtAuthorizer<C> {
|
pub fn from_rsa_pem(path: &'static str) -> JwtAuthorizer<C> {
|
||||||
JwtAuthorizer {
|
JwtAuthorizer {
|
||||||
key_source_type: Some(KeySourceType::RSA(path.to_owned())),
|
key_source_type: KeySourceType::RSA(path.to_owned()),
|
||||||
refresh: Default::default(),
|
refresh: Default::default(),
|
||||||
claims_checker: None,
|
claims_checker: None,
|
||||||
}
|
}
|
||||||
|
|
@ -67,7 +67,7 @@ where
|
||||||
/// Builds Authorizer Layer from a EC PEM file
|
/// Builds Authorizer Layer from a EC PEM file
|
||||||
pub fn from_ec_pem(path: &'static str) -> JwtAuthorizer<C> {
|
pub fn from_ec_pem(path: &'static str) -> JwtAuthorizer<C> {
|
||||||
JwtAuthorizer {
|
JwtAuthorizer {
|
||||||
key_source_type: Some(KeySourceType::EC(path.to_owned())),
|
key_source_type: KeySourceType::EC(path.to_owned()),
|
||||||
refresh: Default::default(),
|
refresh: Default::default(),
|
||||||
claims_checker: None,
|
claims_checker: None,
|
||||||
}
|
}
|
||||||
|
|
@ -76,7 +76,7 @@ where
|
||||||
/// Builds Authorizer Layer from a EC PEM file
|
/// Builds Authorizer Layer from a EC PEM file
|
||||||
pub fn from_ed_pem(path: &'static str) -> JwtAuthorizer<C> {
|
pub fn from_ed_pem(path: &'static str) -> JwtAuthorizer<C> {
|
||||||
JwtAuthorizer {
|
JwtAuthorizer {
|
||||||
key_source_type: Some(KeySourceType::ED(path.to_owned())),
|
key_source_type: KeySourceType::ED(path.to_owned()),
|
||||||
refresh: Default::default(),
|
refresh: Default::default(),
|
||||||
claims_checker: None,
|
claims_checker: None,
|
||||||
}
|
}
|
||||||
|
|
@ -85,7 +85,7 @@ where
|
||||||
/// Builds Authorizer Layer from a secret phrase
|
/// Builds Authorizer Layer from a secret phrase
|
||||||
pub fn from_secret(secret: &'static str) -> JwtAuthorizer<C> {
|
pub fn from_secret(secret: &'static str) -> JwtAuthorizer<C> {
|
||||||
JwtAuthorizer {
|
JwtAuthorizer {
|
||||||
key_source_type: Some(KeySourceType::Secret(secret)),
|
key_source_type: KeySourceType::Secret(secret),
|
||||||
refresh: Default::default(),
|
refresh: Default::default(),
|
||||||
claims_checker: None,
|
claims_checker: None,
|
||||||
}
|
}
|
||||||
|
|
@ -122,31 +122,7 @@ where
|
||||||
|
|
||||||
/// Build axum layer
|
/// Build axum layer
|
||||||
pub async fn layer(&self) -> Result<AsyncAuthorizationLayer<C>, InitError> {
|
pub async fn layer(&self) -> Result<AsyncAuthorizationLayer<C>, InitError> {
|
||||||
let auth = if let Some(ref key_source_type) = self.key_source_type {
|
let auth = Arc::new(Authorizer::build(&self.key_source_type, self.claims_checker.clone(), self.refresh).await?);
|
||||||
match key_source_type {
|
|
||||||
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::Discovery(issuer_url) => {
|
|
||||||
let jwks_url = oidc::discover_jwks(issuer_url).await?;
|
|
||||||
Arc::new(Authorizer::from_jwks_url(
|
|
||||||
&jwks_url,
|
|
||||||
self.claims_checker.clone(),
|
|
||||||
self.refresh.unwrap_or_default(),
|
|
||||||
)?)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return Err(InitError::BuilderError(
|
|
||||||
"No key source to build the layer user from_*() to specify the key source!".to_owned(),
|
|
||||||
));
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(AsyncAuthorizationLayer::new(auth))
|
Ok(AsyncAuthorizationLayer::new(auth))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue