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 #18 from ralvescosta/develop
Browse files Browse the repository at this point in the history
Develop
  • Loading branch information
ralvescosta authored Feb 12, 2023
2 parents b0b5775 + 3d638af commit c9e8c02
Show file tree
Hide file tree
Showing 29 changed files with 812 additions and 145 deletions.
329 changes: 265 additions & 64 deletions Cargo.lock

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,5 @@ members = [
"secrets_manager",
"sql_pool",
"traces",
"auth",
]
16 changes: 16 additions & 0 deletions auth/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
[package]
name = "auth"
version = "0.1.0"
edition = "2021"

[dependencies]
env = { path = "../env" }

serde = { version = "1.0.152", features = ["derive"] }
serde_json = { version = "1.0.93" }
tracing = { version = "0.1.37" }
async-trait = { version = "0.1.64" }
opentelemetry = { version = "0.18.0" }
alcoholic_jwt = { version = "4091.0.0 " }
tokio = { version = "1.25.0", features = ["sync"] }
reqwest = { version = "0.11.14", features = ["json"] }
11 changes: 11 additions & 0 deletions auth/src/defs.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
pub enum UsersScopes {}

pub enum ThingsScopes {}

pub enum PlatformScopes {}

pub enum Scopes {
USER(UsersScopes),
THING(ThingsScopes),
PLATFORM(PlatformScopes),
}
248 changes: 248 additions & 0 deletions auth/src/jwt_manager/auth0.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,248 @@
use super::{JwtManager, TokenClaims};
use alcoholic_jwt::{token_kid, validate, Validation, JWKS};
use async_trait::async_trait;
use env::AppConfig;
use opentelemetry::{
global::{self, BoxedSpan, BoxedTracer},
trace::{Span, Status, Tracer},
Context,
};
use serde_json::Value;
use std::{
borrow::Cow,
sync::Arc,
time::{Duration, SystemTime},
};
use tokio::sync::Mutex;
use tracing::error;

pub struct Auth0JwtManager {
jwks: Mutex<Option<JWKS>>,
jwks_retrieved_at: SystemTime,
authority: String,
tracer: BoxedTracer,
}

impl Auth0JwtManager {
pub fn new(cfg: &AppConfig) -> Arc<Auth0JwtManager> {
Arc::new(Auth0JwtManager {
jwks: Mutex::new(None),
jwks_retrieved_at: SystemTime::now(),
authority: cfg.auth_authority.clone(),
tracer: global::tracer("auth0_middleware"),
})
}
}

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

let jwks = self.retrieve_jwks(&mut span).await?;

let kid = match token_kid(&token) {
Ok(res) => {
if res.is_none() {
error!("token with no kid");
span.set_status(Status::Error {
description: Cow::from("token with no kid"),
});
return Err(());
}

Ok(res.unwrap())
}
Err(err) => {
error!(error = err.to_string(), "error retrieving the token kid");
span.record_error(&err);
span.set_status(Status::Error {
description: Cow::from("error retrieving the token kid"),
});
Err(())
}
}?;

let validations = vec![
Validation::Issuer(self.authority.clone()),
Validation::SubjectPresent,
];

let jwk = match jwks.find(&kid) {
Some(jwk) => Ok(jwk),
_ => {
error!("specified jwk key was not founded in set");
span.set_status(Status::Error {
description: Cow::from("specified jwk key was not founded in set"),
});
Err(())
}
}?;

let claims = match validate(token, jwk, validations) {
Ok(res) => Ok(res.claims),
Err(err) => {
error!(error = err.to_string(), "invalid jwt token");
span.record_error(&err);
span.set_status(Status::Error {
description: Cow::from("invalid jwt token"),
});
Err(())
}
}?;

span.set_status(Status::Ok);

Ok(TokenClaims {
iss: self.get_claim_as_string("iss", &claims, &mut span)?,
sub: self.get_claim_as_string("sub", &claims, &mut span)?,
aud: self.get_claim_as_vec("aud", &claims, &mut span)?,
iat: self.get_claim_as_u64("iat", &claims, &mut span)?,
exp: self.get_claim_as_u64("exp", &claims, &mut span)?,
scope: self.get_claim_as_string("scope", &claims, &mut span)?,
})
}
}

impl Auth0JwtManager {
async fn retrieve_jwks(&self, span: &mut BoxedSpan) -> Result<JWKS, ()> {
let mut jwks = self.jwks.lock().await;

if jwks.is_none() {
let new = self.get_jwks(span).await?;
*jwks = Some(new.clone());
return Ok(new);
}

let duration = match SystemTime::now().duration_since(self.jwks_retrieved_at.clone()) {
Ok(d) => Ok(d),
Err(err) => {
error!(
error = err.to_string(),
"error comparing the jwks caching time"
);
span.record_error(&err);
span.set_status(Status::Error {
description: Cow::from("error comparing the jwks caching time"),
});
Err(())
}
}?;

if duration.cmp(&Duration::new(3600, 0)).is_ge() {
let new = self.get_jwks(span).await?;
*jwks = Some(new.clone());
return Ok(new);
}

Ok(jwks.clone().unwrap())
}

async fn get_jwks(&self, span: &mut BoxedSpan) -> Result<JWKS, ()> {
let res =
match reqwest::get(&format!("{}{}", self.authority, ".well-known/jwks.json")).await {
Err(err) => {
error!(error = err.to_string(), "error to get jwks from auth0 api");
span.record_error(&err);
span.set_status(Status::Error {
description: Cow::from("error to get jwks from auth0 api"),
});
Err(())
}
Ok(r) => Ok(r),
}?;

let val = match res.json::<JWKS>().await {
Err(err) => {
error!(error = err.to_string(), "error deserializing the jwks");
span.record_error(&err);
span.set_status(Status::Error {
description: Cow::from("error deserializing the jwks"),
});
Err(())
}
Ok(v) => Ok(v),
}?;

return Ok(val);
}

fn get_claim_as_string(
&self,
key: &str,
claims: &Value,
span: &mut BoxedSpan,
) -> Result<String, ()> {
match claims.get(key) {
Some(fv) => match fv.as_str() {
Some(value) => Ok(value.to_owned()),
_ => {
error!(claim = key, "invalid jwt claim");
span.set_status(Status::Error {
description: Cow::from("invalid jwt claim"),
});
Err(())
}
},
_ => {
error!(claim = key, "invalid jwt claim");
span.set_status(Status::Error {
description: Cow::from("invalid jwt claim"),
});
Err(())
}
}
}

fn get_claim_as_u64(&self, key: &str, claims: &Value, span: &mut BoxedSpan) -> Result<u64, ()> {
match claims.get(key) {
Some(fv) => match fv.as_u64() {
Some(value) => Ok(value),
_ => {
error!(claim = key, "invalid jwt claim");
span.set_status(Status::Error {
description: Cow::from("invalid jwt claim"),
});
Err(())
}
},
_ => {
error!(claim = key, "invalid jwt claim");
span.set_status(Status::Error {
description: Cow::from("invalid jwt claim"),
});
Err(())
}
}
}

fn get_claim_as_vec(
&self,
key: &str,
claims: &Value,
span: &mut BoxedSpan,
) -> Result<Vec<String>, ()> {
match claims.get(key) {
Some(fv) => match fv.as_array() {
Some(value) => Ok(value
.iter()
.map(|v| v.as_str().unwrap().to_owned())
.collect::<Vec<String>>()),
_ => {
error!(claim = key, "invalid jwt claim");
span.set_status(Status::Error {
description: Cow::from("invalid jwt claim"),
});
Err(())
}
},
_ => {
error!(claim = key, "invalid jwt claim");
span.set_status(Status::Error {
description: Cow::from("invalid jwt claim"),
});
Err(())
}
}
}
}
18 changes: 18 additions & 0 deletions auth/src/jwt_manager/manager.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
use async_trait::async_trait;
use opentelemetry::Context;
use serde::{Deserialize, Serialize};

#[derive(Debug, Default, Serialize, Deserialize)]
pub struct TokenClaims {
pub iss: String,
pub sub: String,
pub aud: Vec<String>,
pub iat: u64,
pub exp: u64,
pub scope: String,
}

#[async_trait]
pub trait JwtManager {
async fn verify(&self, ctx: &Context, token: &str) -> Result<TokenClaims, ()>;
}
4 changes: 4 additions & 0 deletions auth/src/jwt_manager/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
pub mod auth0;
mod manager;

pub use manager::{JwtManager, TokenClaims};
5 changes: 5 additions & 0 deletions auth/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
mod defs;
pub mod jwt_manager;
mod types;

pub use defs::{PlatformScopes, Scopes, ThingsScopes, UsersScopes};
1 change: 1 addition & 0 deletions auth/src/types.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@

2 changes: 2 additions & 0 deletions env/src/configs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@ pub struct AppConfig {
pub log_level: String,
///Default: false
pub enable_external_creates_logging: bool,
///Default:
pub auth_authority: String,
}

impl AppConfig {
Expand Down
21 changes: 12 additions & 9 deletions env/src/configs_builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,16 @@ use crate::{
configs::{AppConfig, Configs, DynamicConfig},
def::{
AMQP_HOST_ENV_KEY, AMQP_PASSWORD_ENV_KEY, AMQP_PORT_ENV_KEY, AMQP_USER_ENV_KEY,
AMQP_VHOST_ENV_KEY, APP_NAME_ENV_KEY, APP_PORT_ENV_KEY, AWS_DEFAULT_REGION,
CUSTOM_AWS_ACCESS_KEY_ID_ENV_KEY, CUSTOM_AWS_SECRET_ACCESS_KEY, DYNAMO_ENDPOINT_ENV_KEY,
DYNAMO_REGION_ENV_KEY, DYNAMO_TABLE_ENV_KEY, ENABLE_HEALTH_READINESS_ENV_KEY,
ENABLE_METRICS_ENV_KEY, ENABLE_TRACES_ENV_KEY, HEALTH_READINESS_PORT_ENV_KEY,
HOST_NAME_ENV_KEY, LOG_LEVEL_ENV_KEY, MQTT_HOST_ENV_KEY, MQTT_PASSWORD_ENV_KEY,
MQTT_PORT_ENV_KEY, MQTT_USER_ENV_KEY, OTLP_ACCESS_KEY_ENV_KEY, OTLP_EXPORT_TIMEOUT_ENV_KEY,
OTLP_HOST_ENV_KEY, OTLP_SERVICE_TYPE_ENV_KEY, POSTGRES_DB_ENV_KEY, POSTGRES_HOST_ENV_KEY,
POSTGRES_PASSWORD_ENV_KEY, POSTGRES_PORT_ENV_KEY, POSTGRES_USER_ENV_KEY,
SECRET_KEY_ENV_KEY, SECRET_PREFIX, SECRET_PREFIX_TO_DECODE, SQLITE_FILE_NAME_ENV_KEY,
AMQP_VHOST_ENV_KEY, APP_NAME_ENV_KEY, APP_PORT_ENV_KEY, AUTH_AUTHORITY_ENV_KEY,
AWS_DEFAULT_REGION, CUSTOM_AWS_ACCESS_KEY_ID_ENV_KEY, CUSTOM_AWS_SECRET_ACCESS_KEY,
DYNAMO_ENDPOINT_ENV_KEY, DYNAMO_REGION_ENV_KEY, DYNAMO_TABLE_ENV_KEY,
ENABLE_HEALTH_READINESS_ENV_KEY, ENABLE_METRICS_ENV_KEY, ENABLE_TRACES_ENV_KEY,
HEALTH_READINESS_PORT_ENV_KEY, HOST_NAME_ENV_KEY, LOG_LEVEL_ENV_KEY, MQTT_HOST_ENV_KEY,
MQTT_PASSWORD_ENV_KEY, MQTT_PORT_ENV_KEY, MQTT_USER_ENV_KEY, OTLP_ACCESS_KEY_ENV_KEY,
OTLP_EXPORT_TIMEOUT_ENV_KEY, OTLP_HOST_ENV_KEY, OTLP_SERVICE_TYPE_ENV_KEY,
POSTGRES_DB_ENV_KEY, POSTGRES_HOST_ENV_KEY, POSTGRES_PASSWORD_ENV_KEY,
POSTGRES_PORT_ENV_KEY, POSTGRES_USER_ENV_KEY, SECRET_KEY_ENV_KEY, SECRET_PREFIX,
SECRET_PREFIX_TO_DECODE, SQLITE_FILE_NAME_ENV_KEY,
},
Environment,
};
Expand Down Expand Up @@ -123,6 +124,7 @@ impl ConfigBuilder {
.parse()
.unwrap_or_default();
let log_level = env::var(LOG_LEVEL_ENV_KEY).unwrap_or("debug".to_owned());
let auth_authority = env::var(AUTH_AUTHORITY_ENV_KEY).unwrap_or("".to_owned());

let app_cfg = AppConfig {
enable_external_creates_logging: false,
Expand All @@ -132,6 +134,7 @@ impl ConfigBuilder {
name,
port,
secret_key,
auth_authority,
};

self.app_cfg = app_cfg.clone();
Expand Down
1 change: 1 addition & 0 deletions env/src/def.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ pub const SECRET_KEY_ENV_KEY: &str = "SECRET_KEY";
pub const HOST_NAME_ENV_KEY: &str = "HOST_NAME";
pub const APP_PORT_ENV_KEY: &str = "APP_PORT";
pub const LOG_LEVEL_ENV_KEY: &str = "LOG_LEVEL";
pub const AUTH_AUTHORITY_ENV_KEY: &str = "AUTH_AUTHORITY";
pub const MQTT_HOST_ENV_KEY: &str = "MQTT_HOST";
pub const MQTT_PORT_ENV_KEY: &str = "MQTT_PORT";
pub const MQTT_USER_ENV_KEY: &str = "MQTT_USER";
Expand Down
4 changes: 2 additions & 2 deletions health_readiness/src/controller.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use std::sync::Arc;

use actix_web::{error::HttpError, get, HttpResponse, web, http::StatusCode};
use httpw::viewmodels::error::HttpErrorViewModel;
use actix_web::{error::HttpError, get, http::StatusCode, web, HttpResponse};
use httpw::viewmodels::HttpErrorViewModel;

use crate::HealthReadinessService;

Expand Down
Loading

0 comments on commit c9e8c02

Please sign in to comment.