Skip to content
This repository has been archived by the owner on Apr 26, 2024. It is now read-only.

Commit

Permalink
Merge pull request #58 from ralvescosta/develop
Browse files Browse the repository at this point in the history
Develop
  • Loading branch information
ralvescosta authored Apr 25, 2024
2 parents db8cbc6 + 0b66793 commit 2288723
Show file tree
Hide file tree
Showing 35 changed files with 217 additions and 203 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ jobs:
sudo apt update \
&& sudo apt install libssl-dev build-essential cmake pkg-config llvm-dev libclang-dev clang libmosquitto-dev libsqlite3-dev -y \
&& cargo install --locked cargo-audit || true \
&& cargo install cargo-tarpaulin
&& cargo install cargo-tarpaulin --force
- name: 🔐 Run audit
run: |
Expand Down
2 changes: 2 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions auth/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ serde_json = { workspace = true }
tracing = { workspace = true }
async-trait = { workspace = true }
opentelemetry = { workspace = true }
thiserror = { workspace = true }
tokio = { workspace = true, features = ["sync"] }
reqwest = { version = "0.12.3", features = ["json"] }
moka = { version = "0.12.7", features = ["future"] }
Expand Down
12 changes: 6 additions & 6 deletions auth/src/auth0.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use super::{manager::JwtManager, types::TokenClaims};
use super::{errors::AuthError, manager::JwtManager, types::TokenClaims};
use async_trait::async_trait;
use configs::IdentityServerConfigs;
use jsonwebtoken::jwk::JwkSet;
Expand Down Expand Up @@ -37,7 +37,7 @@ impl Auth0JwtManager {

#[async_trait]
impl JwtManager for Auth0JwtManager {
async fn verify(&self, ctx: &Context, token: &str) -> Result<TokenClaims, ()> {
async fn verify(&self, ctx: &Context, token: &str) -> Result<TokenClaims, AuthError> {
let mut span = self.tracer.start_with_context("authenticate", ctx);

if let Some(cached_claim) = self.jwt_cache.get(token).await {
Expand Down Expand Up @@ -75,7 +75,7 @@ impl JwtManager for Auth0JwtManager {
}

impl Auth0JwtManager {
async fn get_jwks(&self, span: &mut BoxedSpan) -> Result<JwkSet, ()> {
async fn get_jwks(&self, span: &mut BoxedSpan) -> Result<JwkSet, AuthError> {
let res = match reqwest::get(&format!(
"https://{}/{}",
self.cfg.realm, ".well-known/jwks.json"
Expand All @@ -88,7 +88,7 @@ impl Auth0JwtManager {
span.set_status(Status::Error {
description: Cow::from("error to get jwks from auth0 api"),
});
Err(())
Err(AuthError::CouldNotRetrieveJWKS)
}
Ok(r) => Ok(r),
}?;
Expand All @@ -100,12 +100,12 @@ impl Auth0JwtManager {
span.set_status(Status::Error {
description: Cow::from("error deserializing the jwks"),
});
Err(())
Err(AuthError::FailedToDeserializeToken)
}
Ok(v) => Ok(v),
}?;

return Ok(val);
Ok(val)
}
}

Expand Down
25 changes: 25 additions & 0 deletions auth/src/errors.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
use thiserror::Error;

#[derive(Error, Debug, PartialEq, Eq)]
pub enum AuthError {
#[error("internal error")]
InternalError,

#[error("could not retrieve JWKS")]
CouldNotRetrieveJWKS,

#[error("error to load secrets from secret manager - `{0}`")]
InvalidToken(String),

#[error("failed to deserialize token")]
FailedToDeserializeToken,

#[error("failed to retrieve claim")]
FailedToRetrieveClaim,

#[error("failed to retrieve user custom data claim")]
FailedToRetrieveUserCustomDataClaim,

#[error("failed to retrieve scope claim")]
FailedToRetrieveScopeClaim,
}
12 changes: 6 additions & 6 deletions auth/src/keycloak.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use super::{manager::JwtManager, types::TokenClaims};
use super::{errors::AuthError, manager::JwtManager, types::TokenClaims};
use async_trait::async_trait;
use configs::IdentityServerConfigs;
use jsonwebtoken::jwk::JwkSet;
Expand Down Expand Up @@ -37,7 +37,7 @@ impl KeycloakJwtManager {

#[async_trait]
impl JwtManager for KeycloakJwtManager {
async fn verify(&self, ctx: &Context, token: &str) -> Result<TokenClaims, ()> {
async fn verify(&self, ctx: &Context, token: &str) -> Result<TokenClaims, AuthError> {
let mut span = self.tracer.start_with_context("authenticate", ctx);

if let Some(cached_claim) = self.jwt_cache.get(token).await {
Expand Down Expand Up @@ -75,7 +75,7 @@ impl JwtManager for KeycloakJwtManager {
}

impl KeycloakJwtManager {
async fn get_jwks(&self, span: &mut BoxedSpan) -> Result<JwkSet, ()> {
async fn get_jwks(&self, span: &mut BoxedSpan) -> Result<JwkSet, AuthError> {
// http://BASE_URL/realms/proteu/protocol/openid-connect/certs
let endpoint = format!(
"{}/realms/{}/protocol/openid-connect/certs",
Expand All @@ -89,7 +89,7 @@ impl KeycloakJwtManager {
span.set_status(Status::Error {
description: Cow::from("error to get jwks from auth0 api"),
});
Err(())
Err(AuthError::CouldNotRetrieveJWKS)
}
Ok(r) => Ok(r),
}?;
Expand All @@ -101,11 +101,11 @@ impl KeycloakJwtManager {
span.set_status(Status::Error {
description: Cow::from("error deserializing the jwks"),
});
Err(())
Err(AuthError::CouldNotRetrieveJWKS)
}
Ok(v) => Ok(v),
}?;

return Ok(val);
Ok(val)
}
}
1 change: 1 addition & 0 deletions auth/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
pub mod auth0;
pub mod errors;
pub mod keycloak;
pub mod manager;
pub mod rbac;
Expand Down
32 changes: 21 additions & 11 deletions auth/src/manager.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use crate::types::TokenClaims;
use crate::{errors::AuthError, types::TokenClaims};
use async_trait::async_trait;
use jsonwebtoken::{
decode, decode_header,
Expand All @@ -12,48 +12,58 @@ use tracing::error;

#[async_trait]
pub trait JwtManager: Send + Sync {
async fn verify(&self, ctx: &Context, token: &str) -> Result<TokenClaims, ()>;
async fn verify(&self, ctx: &Context, token: &str) -> Result<TokenClaims, AuthError>;

fn decode_token(
&self,
token: &str,
jwks: &JwkSet,
aud: &str,
iss: &str,
) -> Result<TokenData<HashMap<String, Value>>, ()> {
) -> Result<TokenData<HashMap<String, Value>>, AuthError> {
let Ok(header) = decode_header(token) else {
error!("failed to decoded token header");
return Err(());
return Err(AuthError::InvalidToken(
"failed to decoded token header".into(),
));
};

let Some(kid) = header.kid else {
error!("token header without kid");
return Err(());
return Err(AuthError::InvalidToken("token header without kid".into()));
};

let Some(jwk) = jwks.find(&kid) else {
error!("wasn't possible to find the same token kid into jwks");
return Err(());
return Err(AuthError::InvalidToken(
"wasn't possible to find the same token kid into jwks".into(),
));
};

let AlgorithmParameters::RSA(rsa) = &jwk.algorithm else {
error!("token hashed using other algorithm than RSA");
return Err(());
return Err(AuthError::InvalidToken(
"token hashed using other algorithm than RSA".into(),
));
};

let Ok(decoding_key) = DecodingKey::from_rsa_components(&rsa.n, &rsa.e) else {
error!("failed to decode rsa components");
return Err(());
return Err(AuthError::InvalidToken(
"failed to decode rsa components".into(),
));
};

let Some(key_alg) = jwk.common.key_algorithm else {
error!("jwk with no key algorithm");
return Err(());
return Err(AuthError::InvalidToken("jwk with no key algorithm".into()));
};

let Ok(alg) = Algorithm::from_str(key_alg.to_string().as_str()) else {
error!("algorithm provided by the JWK is not sported!");
return Err(());
return Err(AuthError::InvalidToken(
"algorithm provided by the JWK is not sported!".into(),
));
};

let mut validation = Validation::new(alg);
Expand All @@ -67,7 +77,7 @@ pub trait JwtManager: Send + Sync {
Ok(d) => Ok(d),
Err(err) => {
error!(error = err.to_string(), "token validation error");
Err(())
Err(AuthError::InvalidToken("token validation error".into()))
}
}
}
Expand Down
54 changes: 12 additions & 42 deletions auth/src/types/custom_data.rs
Original file line number Diff line number Diff line change
@@ -1,77 +1,47 @@
use crate::errors::AuthError;
use serde::{Deserialize, Serialize};
use serde_json::Value;
use std::collections::HashMap;
use tracing::error;

#[derive(Debug, Default, Serialize, Deserialize, Clone)]
pub struct CustomData {
#[serde(rename(serialize = "x1", deserialize = "x1"))]
pub user_id: i64,

#[serde(rename(serialize = "x2", deserialize = "x2"))]
pub user_key: String,

#[serde(rename(serialize = "y1", deserialize = "y1"))]
pub company_id: i64,

#[serde(rename(serialize = "y2", deserialize = "y2"))]
pub company_key: String,
pub user_data: Value,
pub user_metadata: Value,
}

impl CustomData {
pub fn from_auth0(claims: &HashMap<String, Value>) -> Result<Self, ()> {
pub fn from_auth0(claims: &HashMap<String, Value>) -> Result<Self, AuthError> {
let Some(user_data) = claims.get("user_data") else {
error!(claim = "user_data", "invalid jwt claim");
return Err(());
return Err(AuthError::FailedToRetrieveUserCustomDataClaim);
};

let Some(user_metadata) = user_data.get("user_metadata") else {
error!(claim = "user_metadata", "invalid jwt claim");
return Err(());
};

let Some(x1) = user_metadata.get("x1") else {
error!(claim = "x1", "invalid jwt claim");
return Err(());
};

let Some(x2) = user_metadata.get("x2") else {
error!(claim = "x2", "invalid jwt claim");
return Err(());
};

let Some(y1) = user_metadata.get("y1") else {
error!(claim = "y1", "invalid jwt claim");
return Err(());
};

let Some(y2) = user_metadata.get("y2") else {
error!(claim = "y2", "invalid jwt claim");
return Err(());
return Err(AuthError::FailedToRetrieveUserCustomDataClaim);
};

Ok(Self {
user_id: x1.as_i64().unwrap(),
user_key: x2.as_str().unwrap().into(),
company_id: y1.as_i64().unwrap(),
company_key: y2.as_str().unwrap().into(),
user_data: user_data.to_owned(),
user_metadata: user_metadata.to_owned(),
})
}

pub fn from_keycloak(claims: &HashMap<String, Value>) -> Result<Self, ()> {
pub fn from_keycloak(claims: &HashMap<String, Value>) -> Result<Self, AuthError> {
let Some(user_data) = claims.get("user_data") else {
error!(claim = "user_data", "invalid jwt claim");
return Err(());
return Err(AuthError::FailedToRetrieveUserCustomDataClaim);
};

let Some(json) = user_data.as_str() else {
error!(claim = "user_data", "invalid jwt claim");
return Err(());
return Err(AuthError::FailedToRetrieveUserCustomDataClaim);
};

let Ok(custom) = serde_json::from_str::<CustomData>(json) else {
error!(claim = "user_data", "invalid jwt claim");
return Err(());
return Err(AuthError::FailedToRetrieveUserCustomDataClaim);
};

Ok(custom)
Expand Down
Loading

0 comments on commit 2288723

Please sign in to comment.