mirror of
https://github.com/TECHNOFAB11/jwt-authorizer.git
synced 2025-12-11 23:50:07 +01:00
feat: building layer from rsa, ec, ed, secret
This commit is contained in:
parent
b0667729a3
commit
9bd99b2a13
5 changed files with 80 additions and 58 deletions
|
|
@ -39,7 +39,7 @@ async fn main() {
|
||||||
|
|
||||||
let api = Router::new()
|
let api = Router::new()
|
||||||
.route("/protected", get(protected))
|
.route("/protected", get(protected))
|
||||||
.layer(jwt_auth.layer());
|
.layer(jwt_auth.layer().unwrap());
|
||||||
// .layer(jwt_auth.check_claims(|_: User| true));
|
// .layer(jwt_auth.check_claims(|_: User| true));
|
||||||
|
|
||||||
let app = Router::new()
|
let app = Router::new()
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,7 @@ Example:
|
||||||
|
|
||||||
// adding the authorization layer
|
// adding the authorization layer
|
||||||
let app = Router::new().route("/protected", get(protected))
|
let app = Router::new().route("/protected", get(protected))
|
||||||
.layer(jwt_auth.layer());
|
.layer(jwt_auth.layer().unwrap());
|
||||||
|
|
||||||
// proteced handler with user injection (mapping some jwt claims)
|
// proteced handler with user injection (mapping some jwt claims)
|
||||||
async fn protected(JwtClaims(user): JwtClaims<User>) -> Result<String, AuthError> {
|
async fn protected(JwtClaims(user): JwtClaims<User>) -> Result<String, AuthError> {
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ use jsonwebtoken::{decode, decode_header, jwk::JwkSet, DecodingKey, TokenData, V
|
||||||
use serde::de::DeserializeOwned;
|
use serde::de::DeserializeOwned;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
error::AuthError,
|
error::{AuthError, InitError},
|
||||||
jwks::{key_store_manager::KeyStoreManager, KeySource},
|
jwks::{key_store_manager::KeyStoreManager, KeySource},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -37,13 +37,21 @@ where
|
||||||
pub claims_checker: Option<FnClaimsChecker<C>>,
|
pub claims_checker: Option<FnClaimsChecker<C>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn read_data(path: &str) -> Result<Vec<u8>, AuthError> {
|
fn read_data(path: &str) -> Result<Vec<u8>, InitError> {
|
||||||
let mut data = Vec::<u8>::new();
|
let mut data = Vec::<u8>::new();
|
||||||
let mut f = std::fs::File::open(path)?;
|
let mut f = std::fs::File::open(path)?;
|
||||||
f.read_to_end(&mut data)?;
|
f.read_to_end(&mut data)?;
|
||||||
Ok(data)
|
Ok(data)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub enum KeySourceType {
|
||||||
|
RSA(String),
|
||||||
|
EC(String),
|
||||||
|
ED(String),
|
||||||
|
Secret(&'static str),
|
||||||
|
Jwks(String),
|
||||||
|
}
|
||||||
|
|
||||||
impl<C> Authorizer<C>
|
impl<C> Authorizer<C>
|
||||||
where
|
where
|
||||||
C: DeserializeOwned + Clone + Send + Sync,
|
C: DeserializeOwned + Clone + Send + Sync,
|
||||||
|
|
@ -58,38 +66,22 @@ where
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn from_rsa_file(path: &str) -> Result<Authorizer<C>, AuthError> {
|
pub fn from(key_source_type: &KeySourceType) -> 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())?),
|
||||||
|
KeySourceType::ED(path) => DecodingKey::from_ed_der(&read_data(path.as_str())?),
|
||||||
|
KeySourceType::Secret(secret) => DecodingKey::from_secret(secret.as_bytes()),
|
||||||
|
KeySourceType::Jwks(_) => panic!("bug: use from_jwks_url() to initialise Authorizer"), // should never hapen
|
||||||
|
};
|
||||||
|
|
||||||
Ok(Authorizer {
|
Ok(Authorizer {
|
||||||
key_source: KeySource::DecodingKeySource(DecodingKey::from_rsa_pem(&read_data(path)?)?),
|
key_source: KeySource::DecodingKeySource(key),
|
||||||
claims_checker: None,
|
claims_checker: None,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn from_ec_file(path: &str) -> Result<Authorizer<C>, AuthError> {
|
pub fn from_jwks_url(url: &str, claims_checker: Option<FnClaimsChecker<C>>) -> Result<Authorizer<C>, InitError> {
|
||||||
let k = DecodingKey::from_ec_der(&read_data(path)?);
|
|
||||||
Ok(Authorizer {
|
|
||||||
key_source: KeySource::DecodingKeySource(k),
|
|
||||||
claims_checker: None,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn from_ed_file(path: &str) -> Result<Authorizer<C>, AuthError> {
|
|
||||||
let k = DecodingKey::from_ed_der(&read_data(path)?);
|
|
||||||
Ok(Authorizer {
|
|
||||||
key_source: KeySource::DecodingKeySource(k),
|
|
||||||
claims_checker: None,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn from_secret(secret: &str) -> Result<Authorizer<C>, AuthError> {
|
|
||||||
let k = DecodingKey::from_secret(secret.as_bytes());
|
|
||||||
Ok(Authorizer {
|
|
||||||
key_source: KeySource::DecodingKeySource(k),
|
|
||||||
claims_checker: None,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn from_jwks_url(url: &str, claims_checker: Option<FnClaimsChecker<C>>) -> Result<Authorizer<C>, AuthError> {
|
|
||||||
let key_store_manager = KeyStoreManager::with_refresh_interval(url, Duration::from_secs(60));
|
let key_store_manager = KeyStoreManager::with_refresh_interval(url, Duration::from_secs(60));
|
||||||
Ok(Authorizer {
|
Ok(Authorizer {
|
||||||
key_source: KeySource::KeyStoreSource(key_store_manager),
|
key_source: KeySource::KeyStoreSource(key_store_manager),
|
||||||
|
|
@ -119,12 +111,12 @@ mod tests {
|
||||||
use jsonwebtoken::{Algorithm, Header};
|
use jsonwebtoken::{Algorithm, Header};
|
||||||
use serde_json::Value;
|
use serde_json::Value;
|
||||||
|
|
||||||
use super::Authorizer;
|
use super::{Authorizer, KeySourceType};
|
||||||
|
|
||||||
#[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_secret("xxxxxx").unwrap();
|
let a = Authorizer::<Value>::from(&KeySourceType::Secret("xxxxxx")).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());
|
||||||
}
|
}
|
||||||
|
|
@ -148,22 +140,22 @@ mod tests {
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn from_file() {
|
async fn from_file() {
|
||||||
let a = Authorizer::<Value>::from_rsa_file("../config/jwtRS256.key.pub").unwrap();
|
let a = Authorizer::<Value>::from(&KeySourceType::RSA("../config/jwtRS256.key.pub".to_owned())).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_ec_file("../config/ec256-public.pem").unwrap();
|
let a = Authorizer::<Value>::from(&KeySourceType::EC("../config/ec256-public.pem".to_owned())).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_ed_file("../config/ed25519-public.pem").unwrap();
|
let a = Authorizer::<Value>::from(&KeySourceType::ED("../config/ed25519-public.pem".to_owned())).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_rsa_file("./config/does-not-exist.pem");
|
let a = Authorizer::<Value>::from(&KeySourceType::RSA("./config/does-not-exist.pem".to_owned()));
|
||||||
println!("{:?}", a.as_ref().err());
|
println!("{:?}", a.as_ref().err());
|
||||||
assert!(a.is_err());
|
assert!(a.is_err());
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,18 @@ use thiserror::Error;
|
||||||
|
|
||||||
use tracing::log::warn;
|
use tracing::log::warn;
|
||||||
|
|
||||||
|
#[derive(Debug, Error)]
|
||||||
|
pub enum InitError {
|
||||||
|
#[error("Builder Error {0}")]
|
||||||
|
BuilderError(String),
|
||||||
|
|
||||||
|
#[error(transparent)]
|
||||||
|
KeyFileError(#[from] std::io::Error),
|
||||||
|
|
||||||
|
#[error(transparent)]
|
||||||
|
KeyFileDecodingError(#[from] jsonwebtoken::errors::Error),
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Error)]
|
#[derive(Debug, Error)]
|
||||||
pub enum AuthError {
|
pub enum AuthError {
|
||||||
#[error(transparent)]
|
#[error(transparent)]
|
||||||
|
|
@ -16,9 +28,6 @@ pub enum AuthError {
|
||||||
#[error(transparent)]
|
#[error(transparent)]
|
||||||
JwksRefreshError(#[from] reqwest::Error),
|
JwksRefreshError(#[from] reqwest::Error),
|
||||||
|
|
||||||
#[error(transparent)]
|
|
||||||
KeyFileError(#[from] std::io::Error),
|
|
||||||
|
|
||||||
#[error("InvalidKey {0}")]
|
#[error("InvalidKey {0}")]
|
||||||
InvalidKey(String),
|
InvalidKey(String),
|
||||||
|
|
||||||
|
|
@ -43,7 +52,6 @@ impl IntoResponse for AuthError {
|
||||||
warn!("AuthError: {}", &self);
|
warn!("AuthError: {}", &self);
|
||||||
let (status, error_message) = match self {
|
let (status, error_message) = match self {
|
||||||
AuthError::JwksRefreshError(e) => (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()),
|
AuthError::JwksRefreshError(e) => (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()),
|
||||||
AuthError::KeyFileError(e) => (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()),
|
|
||||||
AuthError::InvalidKid(msg) => (StatusCode::INTERNAL_SERVER_ERROR, msg),
|
AuthError::InvalidKid(msg) => (StatusCode::INTERNAL_SERVER_ERROR, msg),
|
||||||
AuthError::InvalidTokenHeader(_) => (StatusCode::BAD_REQUEST, self.to_string()),
|
AuthError::InvalidTokenHeader(_) => (StatusCode::BAD_REQUEST, self.to_string()),
|
||||||
AuthError::InvalidToken(_) => (StatusCode::BAD_REQUEST, self.to_string()),
|
AuthError::InvalidToken(_) => (StatusCode::BAD_REQUEST, self.to_string()),
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,8 @@ use std::task::{Context, Poll};
|
||||||
use tower_layer::Layer;
|
use tower_layer::Layer;
|
||||||
use tower_service::Service;
|
use tower_service::Service;
|
||||||
|
|
||||||
use crate::authorizer::{Authorizer, FnClaimsChecker};
|
use crate::authorizer::{Authorizer, FnClaimsChecker, KeySourceType};
|
||||||
|
use crate::error::InitError;
|
||||||
|
|
||||||
/// Authorizer Layer builder
|
/// Authorizer Layer builder
|
||||||
///
|
///
|
||||||
|
|
@ -25,45 +26,54 @@ pub struct JwtAuthorizer<C>
|
||||||
where
|
where
|
||||||
C: Clone + DeserializeOwned,
|
C: Clone + DeserializeOwned,
|
||||||
{
|
{
|
||||||
url: Option<&'static str>,
|
key_source_type: Option<KeySourceType>,
|
||||||
claims_checker: Option<FnClaimsChecker<C>>,
|
claims_checker: Option<FnClaimsChecker<C>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// layer builder
|
/// authorization layer builder
|
||||||
impl<C> JwtAuthorizer<C>
|
impl<C> JwtAuthorizer<C>
|
||||||
where
|
where
|
||||||
C: Clone + DeserializeOwned + Send + Sync,
|
C: Clone + DeserializeOwned + Send + Sync,
|
||||||
{
|
{
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
JwtAuthorizer {
|
JwtAuthorizer {
|
||||||
url: None,
|
key_source_type: None,
|
||||||
claims_checker: None,
|
claims_checker: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Build Authorizer Layer from a JWKS endpoint
|
||||||
pub fn from_jwks_url(mut self, url: &'static str) -> JwtAuthorizer<C> {
|
pub fn from_jwks_url(mut self, url: &'static str) -> JwtAuthorizer<C> {
|
||||||
self.url = Some(url);
|
self.key_source_type = Some(KeySourceType::Jwks(url.to_owned()));
|
||||||
|
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Build Authorizer Layer from a RSA PEM file
|
||||||
pub fn from_rsa_pem(mut self, path: &'static str) -> JwtAuthorizer<C> {
|
pub fn from_rsa_pem(mut self, path: &'static str) -> JwtAuthorizer<C> {
|
||||||
// TODO
|
self.key_source_type = Some(KeySourceType::RSA(path.to_owned()));
|
||||||
|
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn from_ec_der(mut self, path: &'static str) -> JwtAuthorizer<C> {
|
/// Build Authorizer Layer from a EC PEM file
|
||||||
// TODO
|
pub fn from_ec_pem(mut self, path: &'static str) -> JwtAuthorizer<C> {
|
||||||
|
self.key_source_type = Some(KeySourceType::EC(path.to_owned()));
|
||||||
|
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn from_ed_der(mut self, path: &'static str) -> JwtAuthorizer<C> {
|
/// Build Authorizer Layer from a EC PEM file
|
||||||
// TODO
|
pub fn from_ed_pem(mut self, path: &'static str) -> JwtAuthorizer<C> {
|
||||||
|
self.key_source_type = Some(KeySourceType::ED(path.to_owned()));
|
||||||
|
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn from_secret(mut self, path: &'static str) -> JwtAuthorizer<C> {
|
/// Build Authorizer Layer from a secret phrase
|
||||||
// TODO
|
pub fn from_secret(mut self, secret: &'static str) -> JwtAuthorizer<C> {
|
||||||
|
self.key_source_type = Some(KeySourceType::Secret(secret));
|
||||||
|
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -74,12 +84,24 @@ where
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// build axum layer
|
/// Build axum layer
|
||||||
pub fn layer(&self) -> AsyncAuthorizationLayer<C> {
|
pub fn layer(&self) -> Result<AsyncAuthorizationLayer<C>, InitError> {
|
||||||
// TODO: replace unwrap
|
let auth = if let Some(ref key_source_type) = self.key_source_type {
|
||||||
let auth = Arc::new(Authorizer::from_jwks_url(self.url.unwrap(), self.claims_checker.clone()).unwrap());
|
match key_source_type {
|
||||||
|
KeySourceType::RSA(_) | KeySourceType::EC(_) | KeySourceType::ED(_) | KeySourceType::Secret(_) => {
|
||||||
|
Arc::new(Authorizer::from(key_source_type)?)
|
||||||
|
}
|
||||||
|
KeySourceType::Jwks(url) => {
|
||||||
|
Arc::new(Authorizer::from_jwks_url(url.as_str(), self.claims_checker.clone())?)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return Err(InitError::BuilderError(
|
||||||
|
"No key source to build the layer user from_*() to specify the key source!".to_owned(),
|
||||||
|
));
|
||||||
|
};
|
||||||
|
|
||||||
AsyncAuthorizationLayer::new(auth)
|
Ok(AsyncAuthorizationLayer::new(auth))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue