This repository has been archived by the owner on Apr 26, 2024. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #18 from ralvescosta/develop
Develop
- Loading branch information
Showing
29 changed files
with
812 additions
and
145 deletions.
There are no files selected for viewing
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -13,4 +13,5 @@ members = [ | |
"secrets_manager", | ||
"sql_pool", | ||
"traces", | ||
"auth", | ||
] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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"] } |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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), | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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(()) | ||
} | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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, ()>; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
pub mod auth0; | ||
mod manager; | ||
|
||
pub use manager::{JwtManager, TokenClaims}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.