From 022c954e5e7cfbe556b0ba5900937ea6983073cd Mon Sep 17 00:00:00 2001 From: ralvescosta Date: Tue, 7 Feb 2023 10:30:26 -0300 Subject: [PATCH 01/10] feat: auth0 middleware --- Cargo.lock | 190 +++++++++++++++++++ Cargo.toml | 1 + auth/Cargo.toml | 14 ++ auth/src/auth0_middleware.rs | 157 +++++++++++++++ auth/src/defs.rs | 11 ++ auth/src/lib.rs | 7 + auth/src/old.rs | 39 ++++ auth/src/types.rs | 14 ++ httpw/Cargo.toml | 3 + httpw/src/authentication/mod.rs | 1 + httpw/src/authentication/token_validation.rs | 36 ++++ httpw/src/lib.rs | 1 + httpw/src/server/server.rs | 36 +++- 13 files changed, 501 insertions(+), 9 deletions(-) create mode 100644 auth/Cargo.toml create mode 100644 auth/src/auth0_middleware.rs create mode 100644 auth/src/defs.rs create mode 100644 auth/src/lib.rs create mode 100644 auth/src/old.rs create mode 100644 auth/src/types.rs create mode 100644 httpw/src/authentication/mod.rs create mode 100644 httpw/src/authentication/token_validation.rs diff --git a/Cargo.lock b/Cargo.lock index 6a1eed9..cbc30f5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -198,6 +198,21 @@ dependencies = [ "syn", ] +[[package]] +name = "actix-web-httpauth" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6dda62cf04bc3a9ad2ea8f314f721951cfdb4cdacec4e984d20e77c7bb170991" +dependencies = [ + "actix-utils", + "actix-web", + "base64 0.13.1", + "futures-core", + "futures-util", + "log", + "pin-project-lite", +] + [[package]] name = "adler" version = "1.0.2" @@ -236,6 +251,19 @@ dependencies = [ "memchr", ] +[[package]] +name = "alcoholic_jwt" +version = "4091.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb8a1270ac7729a671a2d0f7d959e6572330d4da9f6b797165f6b08244019b9e" +dependencies = [ + "base64 0.13.1", + "openssl", + "serde", + "serde_derive", + "serde_json", +] + [[package]] name = "alloc-no-stdlib" version = "2.0.4" @@ -470,6 +498,19 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "debc29dde2e69f9e47506b525f639ed42300fc014a3e007832592448fa8e4599" +[[package]] +name = "auth" +version = "0.1.0" +dependencies = [ + "alcoholic_jwt", + "async-trait", + "env", + "opentelemetry", + "reqwest", + "serde", + "tracing", +] + [[package]] name = "autocfg" version = "1.1.0" @@ -1359,6 +1400,21 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +[[package]] +name = "foreign-types" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +dependencies = [ + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-shared" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" + [[package]] name = "form_urlencoded" version = "1.1.0" @@ -1643,8 +1699,11 @@ version = "0.1.0" dependencies = [ "actix-cors", "actix-web", + "actix-web-httpauth", + "auth", "env", "errors", + "opentelemetry", "serde", "thiserror", "tracing", @@ -1701,6 +1760,19 @@ dependencies = [ "tokio-io-timeout", ] +[[package]] +name = "hyper-tls" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" +dependencies = [ + "bytes", + "hyper", + "native-tls", + "tokio", + "tokio-native-tls", +] + [[package]] name = "iana-time-zone" version = "0.1.53" @@ -1764,6 +1836,12 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "ipnet" +version = "2.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30e22bd8629359895450b59ea7a776c850561b96a3b1d31321c1949d9e6c9146" + [[package]] name = "itertools" version = "0.10.5" @@ -2037,6 +2115,24 @@ version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e5ce46fe64a9d73be07dcbe690a38ce1b293be448fd8ce1e6c1b8062c9f72c6a" +[[package]] +name = "native-tls" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07226173c32f2926027b63cce4bcd8076c3552846cbe7925f3aaffeac0a3b92e" +dependencies = [ + "lazy_static", + "libc", + "log", + "openssl", + "openssl-probe", + "openssl-sys", + "schannel", + "security-framework", + "security-framework-sys", + "tempfile", +] + [[package]] name = "nom" version = "7.1.3" @@ -2098,6 +2194,32 @@ version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6f61fba1741ea2b3d6a1e3178721804bb716a68a6aeba1149b5d52e3d464ea66" +[[package]] +name = "openssl" +version = "0.10.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b102428fd03bc5edf97f62620f7298614c45cedf287c271e7ed450bbaf83f2e1" +dependencies = [ + "bitflags", + "cfg-if", + "foreign-types", + "libc", + "once_cell", + "openssl-macros", + "openssl-sys", +] + +[[package]] +name = "openssl-macros" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b501e44f11665960c7e7fcf062c7d96a14ade4aa98116c004b2e37b5be7d736c" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "openssl-probe" version = "0.1.5" @@ -2614,6 +2736,43 @@ dependencies = [ "winapi", ] +[[package]] +name = "reqwest" +version = "0.11.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21eed90ec8570952d53b772ecf8f206aa1ec9a3d76b2521c56c42973f2d91ee9" +dependencies = [ + "base64 0.21.0", + "bytes", + "encoding_rs", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "hyper", + "hyper-tls", + "ipnet", + "js-sys", + "log", + "mime", + "native-tls", + "once_cell", + "percent-encoding", + "pin-project-lite", + "serde", + "serde_json", + "serde_urlencoded", + "tokio", + "tokio-native-tls", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "winreg", +] + [[package]] name = "retain_mut" version = "0.1.9" @@ -3115,6 +3274,16 @@ dependencies = [ "syn", ] +[[package]] +name = "tokio-native-tls" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7d995660bd2b7f8c1568414c1126076c13fbb725c40112dc0120b78eb9b717b" +dependencies = [ + "native-tls", + "tokio", +] + [[package]] name = "tokio-postgres" version = "0.7.7" @@ -3516,6 +3685,18 @@ dependencies = [ "wasm-bindgen-shared", ] +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23639446165ca5a5de86ae1d8896b737ae80319560fbaa4c2887b7da6e7ebd7d" +dependencies = [ + "cfg-if", + "js-sys", + "wasm-bindgen", + "web-sys", +] + [[package]] name = "wasm-bindgen-macro" version = "0.2.83" @@ -3673,6 +3854,15 @@ version = "0.42.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "447660ad36a13288b1db4d4248e857b510e8c3a225c822ba4fb748c0aafecffd" +[[package]] +name = "winreg" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80d0f4e272c85def139476380b12f9ac60926689dd2e01d4923222f40580869d" +dependencies = [ + "winapi", +] + [[package]] name = "xmlparser" version = "0.13.5" diff --git a/Cargo.toml b/Cargo.toml index 2e377fd..614de52 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,4 +13,5 @@ members = [ "secrets_manager", "sql_pool", "traces", + "auth", ] \ No newline at end of file diff --git a/auth/Cargo.toml b/auth/Cargo.toml new file mode 100644 index 0000000..3d50dfd --- /dev/null +++ b/auth/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "auth" +version = "0.1.0" +edition = "2021" + +[dependencies] +env = { path = "../env" } + +serde = { version = "1.0.152", features = ["derive"] } +alcoholic_jwt = { version = "4091.0.0" } +reqwest = { version = "0.11.14", features = ["json"] } +async-trait = { version = "0.1.64" } +tracing = { version = "0.1.37" } +opentelemetry = { version = "0.18.0" } diff --git a/auth/src/auth0_middleware.rs b/auth/src/auth0_middleware.rs new file mode 100644 index 0000000..be1c39a --- /dev/null +++ b/auth/src/auth0_middleware.rs @@ -0,0 +1,157 @@ +use crate::{defs::Scopes, types::AuthMiddleware}; +use alcoholic_jwt::{token_kid, validate, Validation, JWKS}; +use async_trait::async_trait; +use env::{Configs, Empty}; +use opentelemetry::{ + global::{self, BoxedSpan, BoxedTracer}, + trace::{Span, Status, Tracer}, + Context, +}; +use std::{ + borrow::Cow, + time::{Duration, SystemTime}, +}; +use tracing::error; + +pub struct Auth0Middleware { + jwks: Option, + jwks_retrieved_at: SystemTime, + authority: String, + tracer: BoxedTracer, +} + +impl Auth0Middleware { + pub fn new(cfg: &Configs) -> Auth0Middleware { + Auth0Middleware { + jwks: None, + jwks_retrieved_at: SystemTime::now(), + authority: String::new(), + tracer: global::tracer("auth0_middleware"), + } + } +} + +#[async_trait] +impl AuthMiddleware for Auth0Middleware { + async fn authenticate(&mut self, ctx: &Context, token: &str) -> Result { + 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 = jwks.find(&kid).expect("Specified key not found in set"); + let res = validate(token, jwk, validations); + + Ok(res.is_ok()) + } + + async fn authorize( + &mut self, + ctx: &Context, + token: &str, + required_scope: Scopes, + ) -> Result { + let mut span = self.tracer.start_with_context("authenticate", ctx); + + let jwks = self.retrieve_jwks(&mut span).await?; + + Ok(true) + } +} + +impl Auth0Middleware { + async fn retrieve_jwks(&mut self, span: &mut BoxedSpan) -> Result { + if self.jwks.is_none() { + let jwks = self.get_jwks(span).await?; + self.jwks = Some(jwks.clone()); + return Ok(jwks); + } + + 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 jwks = self.get_jwks(span).await?; + self.jwks = Some(jwks.clone()); + return Ok(jwks); + } + + Ok(self.jwks.clone().unwrap()) + } + + async fn get_jwks(&self, span: &mut BoxedSpan) -> Result { + let res = match reqwest::get("").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::().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); + } +} diff --git a/auth/src/defs.rs b/auth/src/defs.rs new file mode 100644 index 0000000..06ca17f --- /dev/null +++ b/auth/src/defs.rs @@ -0,0 +1,11 @@ +pub enum UsersScopes {} + +pub enum ThingsScopes {} + +pub enum PlatformScopes {} + +pub enum Scopes { + USER(UsersScopes), + THING(ThingsScopes), + PLATFORM(PlatformScopes), +} diff --git a/auth/src/lib.rs b/auth/src/lib.rs new file mode 100644 index 0000000..bd4323e --- /dev/null +++ b/auth/src/lib.rs @@ -0,0 +1,7 @@ +pub mod auth0_middleware; +mod defs; +mod old; +mod types; + +pub use defs::{PlatformScopes, Scopes, ThingsScopes, UsersScopes}; +pub use types::AuthMiddleware; diff --git a/auth/src/old.rs b/auth/src/old.rs new file mode 100644 index 0000000..90fb1be --- /dev/null +++ b/auth/src/old.rs @@ -0,0 +1,39 @@ +use alcoholic_jwt::{token_kid, validate, Validation, JWKS}; +use serde::{Deserialize, Serialize}; +use std::error::Error; + +#[derive(Debug, Serialize, Deserialize)] +struct Claims { + sub: String, + company: String, + exp: usize, +} + +pub async fn validate_token(token: &str) -> Result { + let authority = std::env::var("AUTHORITY").expect("AUTHORITY must be set"); + + let jwks = fetch_jwks(&format!( + "{}{}", + authority.as_str(), + ".well-known/jwks.json" + )) + .await + .expect("failed to fetch jwks"); + + let kid = match token_kid(&token) { + Ok(res) => res.expect("failed to decode kid"), + Err(_) => return Err(()), + }; + + let validations = vec![Validation::Issuer(authority), Validation::SubjectPresent]; + let jwk = jwks.find(&kid).expect("Specified key not found in set"); + let res = validate(token, jwk, validations); + + Ok(res.is_ok()) +} + +async fn fetch_jwks(uri: &str) -> Result> { + let res = reqwest::get(uri).await?; + let val = res.json::().await?; + return Ok(val); +} diff --git a/auth/src/types.rs b/auth/src/types.rs new file mode 100644 index 0000000..9c94b93 --- /dev/null +++ b/auth/src/types.rs @@ -0,0 +1,14 @@ +use crate::defs::Scopes; +use async_trait::async_trait; +use opentelemetry::Context; + +#[async_trait] +pub trait AuthMiddleware { + async fn authenticate(&mut self, ctx: &Context, token: &str) -> Result; + async fn authorize( + &mut self, + ctx: &Context, + token: &str, + required_scope: Scopes, + ) -> Result; +} diff --git a/httpw/Cargo.toml b/httpw/Cargo.toml index d13523a..1c5621f 100644 --- a/httpw/Cargo.toml +++ b/httpw/Cargo.toml @@ -6,10 +6,13 @@ edition = "2021" [dependencies] errors = { path = '../errors' } env = { path = '../env' } +auth = { path = "../auth" } # health-readiness = { path = '../health_readiness' } thiserror = { version = "1.0.37" } actix-web = { version = "4.3.0" } +actix-web-httpauth = "0.8.0" actix-cors = { version = "0.6.3" } serde = { version = "1.0.152", features = ["derive"] } tracing = { version = "0.1.37" } +opentelemetry = { version = "0.18.0" } diff --git a/httpw/src/authentication/mod.rs b/httpw/src/authentication/mod.rs new file mode 100644 index 0000000..d7cdd4e --- /dev/null +++ b/httpw/src/authentication/mod.rs @@ -0,0 +1 @@ +pub mod token_validation; diff --git a/httpw/src/authentication/token_validation.rs b/httpw/src/authentication/token_validation.rs new file mode 100644 index 0000000..09974ac --- /dev/null +++ b/httpw/src/authentication/token_validation.rs @@ -0,0 +1,36 @@ +use std::sync::Arc; + +use actix_web::{dev::ServiceRequest, Error}; +use actix_web_httpauth::extractors::bearer::{BearerAuth, Config}; +use actix_web_httpauth::extractors::AuthenticationError; +use auth::AuthMiddleware; +use opentelemetry::Context; + +async fn validator( + req: ServiceRequest, + credentials: BearerAuth, +) -> Result { + let auth_mid = req + .app_data::>() + .map(|data| data.clone()) + .unwrap(); + + let config = req + .app_data::() + .map(|data| data.clone()) + .unwrap_or_else(Default::default); + + match auth_mid + .authenticate(&Context::new(), credentials.token()) + .await + { + Ok(res) => { + if res == true { + Ok(req) + } else { + Err((AuthenticationError::from(config).into(), req)) + } + } + Err(_) => Err((AuthenticationError::from(config).into(), req)), + } +} diff --git a/httpw/src/lib.rs b/httpw/src/lib.rs index 5d1a15a..e282251 100644 --- a/httpw/src/lib.rs +++ b/httpw/src/lib.rs @@ -1,3 +1,4 @@ +mod authentication; pub mod middlewares; pub mod server; pub mod viewmodels; diff --git a/httpw/src/server/server.rs b/httpw/src/server/server.rs index d9665ba..81884fa 100644 --- a/httpw/src/server/server.rs +++ b/httpw/src/server/server.rs @@ -3,32 +3,42 @@ use crate::middlewares; use actix_web::{middleware as actix_middleware, web, App, HttpServer}; use env::AppConfig as AppEnv; use errors::http_server::HttpServerError; -use std::sync::Arc; use tracing::error; pub struct HttpwServerImpl { - services: Vec, + services_without_auth: Vec, + services_with_auth: Vec, + with_auth: bool, addr: String, } impl HttpwServerImpl { - pub fn new(cfg: &AppEnv) -> Arc { - Arc::new(HttpwServerImpl { - services: vec![], + pub fn new(cfg: &AppEnv) -> HttpwServerImpl { + HttpwServerImpl { + services_without_auth: vec![], + services_with_auth: vec![], + with_auth: false, addr: cfg.app_addr(), - }) + } } } impl HttpwServerImpl { pub fn register(mut self, service: AppConfig) -> Self { - self.services.push(service); + if self.with_auth { + self.services_with_auth.push(service); + } else { + self.services_without_auth.push(service); + } self } pub async fn start(&self) -> Result<(), HttpServerError> { HttpServer::new({ - let services = self.services.to_vec(); + let with_auth = self.with_auth; + let services_without_auth = self.services_without_auth.to_vec(); + let services_with_auth = self.services_with_auth.to_vec(); + move || { let mut app = App::new() .wrap(actix_middleware::Compress::default()) @@ -36,7 +46,15 @@ impl HttpwServerImpl { .wrap(middlewares::cors::config()) .wrap(actix_middleware::Logger::default()); - for svc in services.clone() { + for svc in services_without_auth.clone() { + app = app.configure(svc); + } + + if with_auth { + //apply auth strategy + } + + for svc in services_with_auth.clone() { app = app.configure(svc); } From d51dace8d063d5f1283b4e08ed2160e7ffac39a7 Mon Sep 17 00:00:00 2001 From: ralvescosta Date: Tue, 7 Feb 2023 14:14:38 -0300 Subject: [PATCH 02/10] refactor: auth0 middleware --- Cargo.lock | 1 + auth/Cargo.toml | 1 + auth/src/auth0_middleware.rs | 31 +++++++++++++++++-------------- auth/src/types.rs | 4 ++-- 4 files changed, 21 insertions(+), 16 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index cbc30f5..4210b50 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -508,6 +508,7 @@ dependencies = [ "opentelemetry", "reqwest", "serde", + "tokio", "tracing", ] diff --git a/auth/Cargo.toml b/auth/Cargo.toml index 3d50dfd..7d9ea02 100644 --- a/auth/Cargo.toml +++ b/auth/Cargo.toml @@ -12,3 +12,4 @@ reqwest = { version = "0.11.14", features = ["json"] } async-trait = { version = "0.1.64" } tracing = { version = "0.1.37" } opentelemetry = { version = "0.18.0" } +tokio = { version = "1.25.0", features = ["sync"] } diff --git a/auth/src/auth0_middleware.rs b/auth/src/auth0_middleware.rs index be1c39a..816bde0 100644 --- a/auth/src/auth0_middleware.rs +++ b/auth/src/auth0_middleware.rs @@ -11,10 +11,11 @@ use std::{ borrow::Cow, time::{Duration, SystemTime}, }; +use tokio::sync::Mutex; use tracing::error; pub struct Auth0Middleware { - jwks: Option, + jwks: Mutex>, jwks_retrieved_at: SystemTime, authority: String, tracer: BoxedTracer, @@ -23,9 +24,9 @@ pub struct Auth0Middleware { impl Auth0Middleware { pub fn new(cfg: &Configs) -> Auth0Middleware { Auth0Middleware { - jwks: None, + jwks: Mutex::new(None), jwks_retrieved_at: SystemTime::now(), - authority: String::new(), + authority: String::new(), //get from the config tracer: global::tracer("auth0_middleware"), } } @@ -33,7 +34,7 @@ impl Auth0Middleware { #[async_trait] impl AuthMiddleware for Auth0Middleware { - async fn authenticate(&mut self, ctx: &Context, token: &str) -> Result { + async fn authenticate(&self, ctx: &Context, token: &str) -> Result { let mut span = self.tracer.start_with_context("authenticate", ctx); let jwks = self.retrieve_jwks(&mut span).await?; @@ -76,7 +77,7 @@ impl AuthMiddleware for Auth0Middleware { } async fn authorize( - &mut self, + &self, ctx: &Context, token: &str, required_scope: Scopes, @@ -90,11 +91,13 @@ impl AuthMiddleware for Auth0Middleware { } impl Auth0Middleware { - async fn retrieve_jwks(&mut self, span: &mut BoxedSpan) -> Result { - if self.jwks.is_none() { - let jwks = self.get_jwks(span).await?; - self.jwks = Some(jwks.clone()); - return Ok(jwks); + async fn retrieve_jwks(&self, span: &mut BoxedSpan) -> Result { + 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()) { @@ -115,12 +118,12 @@ impl Auth0Middleware { }?; if duration.cmp(&Duration::new(3600, 0)).is_ge() { - let jwks = self.get_jwks(span).await?; - self.jwks = Some(jwks.clone()); - return Ok(jwks); + let new = self.get_jwks(span).await?; + *jwks = Some(new.clone()); + return Ok(new); } - Ok(self.jwks.clone().unwrap()) + Ok(jwks.clone().unwrap()) } async fn get_jwks(&self, span: &mut BoxedSpan) -> Result { diff --git a/auth/src/types.rs b/auth/src/types.rs index 9c94b93..cbf8ea9 100644 --- a/auth/src/types.rs +++ b/auth/src/types.rs @@ -4,9 +4,9 @@ use opentelemetry::Context; #[async_trait] pub trait AuthMiddleware { - async fn authenticate(&mut self, ctx: &Context, token: &str) -> Result; + async fn authenticate(&self, ctx: &Context, token: &str) -> Result; async fn authorize( - &mut self, + &self, ctx: &Context, token: &str, required_scope: Scopes, From f4319d62e5cae130cfd604f6dc21808b7a782221 Mon Sep 17 00:00:00 2001 From: ralvescosta Date: Tue, 7 Feb 2023 22:13:54 -0300 Subject: [PATCH 03/10] refactor: auth0 middleware --- auth/src/dummy_middleware.rs | 28 ++++++++++++++++++++ auth/src/lib.rs | 1 + httpw/src/authentication/token_validation.rs | 4 +-- httpw/src/server/server.rs | 12 ++++++++- 4 files changed, 42 insertions(+), 3 deletions(-) create mode 100644 auth/src/dummy_middleware.rs diff --git a/auth/src/dummy_middleware.rs b/auth/src/dummy_middleware.rs new file mode 100644 index 0000000..54f3cef --- /dev/null +++ b/auth/src/dummy_middleware.rs @@ -0,0 +1,28 @@ +use crate::{AuthMiddleware, Scopes}; +use async_trait::async_trait; +use opentelemetry::Context; +use std::sync::Arc; + +pub struct DummyMiddleware; + +impl DummyMiddleware { + pub fn new() -> Arc { + Arc::new(DummyMiddleware {}) + } +} + +#[async_trait] +impl AuthMiddleware for DummyMiddleware { + async fn authenticate(&self, _ctx: &Context, _token: &str) -> Result { + Ok(true) + } + + async fn authorize( + &self, + _ctx: &Context, + _token: &str, + _required_scope: Scopes, + ) -> Result { + Ok(true) + } +} diff --git a/auth/src/lib.rs b/auth/src/lib.rs index bd4323e..1925fba 100644 --- a/auth/src/lib.rs +++ b/auth/src/lib.rs @@ -1,5 +1,6 @@ pub mod auth0_middleware; mod defs; +pub mod dummy_middleware; mod old; mod types; diff --git a/httpw/src/authentication/token_validation.rs b/httpw/src/authentication/token_validation.rs index 09974ac..a62199d 100644 --- a/httpw/src/authentication/token_validation.rs +++ b/httpw/src/authentication/token_validation.rs @@ -6,12 +6,12 @@ use actix_web_httpauth::extractors::AuthenticationError; use auth::AuthMiddleware; use opentelemetry::Context; -async fn validator( +pub async fn validator( req: ServiceRequest, credentials: BearerAuth, ) -> Result { let auth_mid = req - .app_data::>() + .app_data::>() .map(|data| data.clone()) .unwrap(); diff --git a/httpw/src/server/server.rs b/httpw/src/server/server.rs index 81884fa..41f4b60 100644 --- a/httpw/src/server/server.rs +++ b/httpw/src/server/server.rs @@ -1,6 +1,10 @@ +use std::sync::Arc; + use super::types::AppConfig; -use crate::middlewares; +use crate::{authentication::token_validation, middlewares}; use actix_web::{middleware as actix_middleware, web, App, HttpServer}; +use actix_web_httpauth::middleware::HttpAuthentication; +use auth::{dummy_middleware::DummyMiddleware, AuthMiddleware}; use env::AppConfig as AppEnv; use errors::http_server::HttpServerError; use tracing::error; @@ -9,6 +13,7 @@ pub struct HttpwServerImpl { services_without_auth: Vec, services_with_auth: Vec, with_auth: bool, + auth_strategy: Arc, addr: String, } @@ -18,6 +23,7 @@ impl HttpwServerImpl { services_without_auth: vec![], services_with_auth: vec![], with_auth: false, + auth_strategy: DummyMiddleware::new(), addr: cfg.app_addr(), } } @@ -38,12 +44,14 @@ impl HttpwServerImpl { let with_auth = self.with_auth; let services_without_auth = self.services_without_auth.to_vec(); let services_with_auth = self.services_with_auth.to_vec(); + let auth_strategy = self.auth_strategy.clone(); move || { let mut app = App::new() .wrap(actix_middleware::Compress::default()) .wrap(middlewares::headers::config()) .wrap(middlewares::cors::config()) + .app_data(web::Data::new(auth_strategy.clone())) .wrap(actix_middleware::Logger::default()); for svc in services_without_auth.clone() { @@ -52,6 +60,8 @@ impl HttpwServerImpl { if with_auth { //apply auth strategy + let auth_mid = HttpAuthentication::bearer(token_validation::validator); + // app = app.wrap(middlewares::headers::config()); } for svc in services_with_auth.clone() { From ca43ec0f38574770e1e5e60141e67bf1147bbe65 Mon Sep 17 00:00:00 2001 From: ralvescosta Date: Wed, 8 Feb 2023 16:55:03 -0300 Subject: [PATCH 04/10] refactor: auth0 middleware --- Cargo.lock | 1 - health_readiness/src/controller.rs | 4 +-- httpw/Cargo.toml | 2 -- httpw/src/errors/http.rs | 50 +++++++++++++++++++++++++++ httpw/src/errors/mod.rs | 5 +++ httpw/src/errors/server.rs | 10 ++++++ httpw/src/lib.rs | 1 + httpw/src/middlewares/deserializer.rs | 4 +-- httpw/src/middlewares/not_found.rs | 16 ++++----- httpw/src/server/server.rs | 24 ++++++++----- httpw/src/server/types.rs | 15 ++++++++ httpw/src/viewmodels/error.rs | 36 +------------------ httpw/src/viewmodels/mod.rs | 4 ++- 13 files changed, 112 insertions(+), 60 deletions(-) create mode 100644 httpw/src/errors/http.rs create mode 100644 httpw/src/errors/mod.rs create mode 100644 httpw/src/errors/server.rs diff --git a/Cargo.lock b/Cargo.lock index 4210b50..99e3f1c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1703,7 +1703,6 @@ dependencies = [ "actix-web-httpauth", "auth", "env", - "errors", "opentelemetry", "serde", "thiserror", diff --git a/health_readiness/src/controller.rs b/health_readiness/src/controller.rs index eee0e79..b16d112 100644 --- a/health_readiness/src/controller.rs +++ b/health_readiness/src/controller.rs @@ -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; diff --git a/httpw/Cargo.toml b/httpw/Cargo.toml index 1c5621f..eb5fc4f 100644 --- a/httpw/Cargo.toml +++ b/httpw/Cargo.toml @@ -4,10 +4,8 @@ version = "0.1.0" edition = "2021" [dependencies] -errors = { path = '../errors' } env = { path = '../env' } auth = { path = "../auth" } -# health-readiness = { path = '../health_readiness' } thiserror = { version = "1.0.37" } actix-web = { version = "4.3.0" } diff --git a/httpw/src/errors/http.rs b/httpw/src/errors/http.rs new file mode 100644 index 0000000..acf4b6c --- /dev/null +++ b/httpw/src/errors/http.rs @@ -0,0 +1,50 @@ +use actix_web::{ + error::ResponseError, + http::{header::ContentType, StatusCode}, + HttpResponse, +}; +use thiserror::Error; + +#[derive(Debug, Error)] +pub enum HttpError { + #[error("bad request")] + BadRequest, + + #[error("unauthorized")] + Unauthorized, + + #[error("forbidden")] + Forbidden, + + #[error("not found")] + NotFound, + + #[error("conflict")] + Conflict, + + #[error("internal error")] + InternalError, + + #[error("timeout")] + Timeout, +} + +impl ResponseError for HttpError { + fn error_response(&self) -> HttpResponse { + HttpResponse::build(self.status_code()) + .insert_header(ContentType::json()) + .body(self.to_string()) + } + + fn status_code(&self) -> StatusCode { + match *self { + HttpError::BadRequest => StatusCode::BAD_REQUEST, + HttpError::Unauthorized => StatusCode::UNAUTHORIZED, + HttpError::Forbidden => StatusCode::FORBIDDEN, + HttpError::NotFound => StatusCode::NOT_FOUND, + HttpError::Conflict => StatusCode::CONFLICT, + HttpError::InternalError => StatusCode::INTERNAL_SERVER_ERROR, + HttpError::Timeout => StatusCode::GATEWAY_TIMEOUT, + } + } +} diff --git a/httpw/src/errors/mod.rs b/httpw/src/errors/mod.rs new file mode 100644 index 0000000..b02c173 --- /dev/null +++ b/httpw/src/errors/mod.rs @@ -0,0 +1,5 @@ +mod http; +mod server; + +pub use http::HttpError; +pub use server::HttpServerError; diff --git a/httpw/src/errors/server.rs b/httpw/src/errors/server.rs new file mode 100644 index 0000000..276c77f --- /dev/null +++ b/httpw/src/errors/server.rs @@ -0,0 +1,10 @@ +use thiserror::Error; + +#[derive(Debug, Error)] +pub enum HttpServerError { + #[error("http port binding error")] + PortBidingError, + + #[error("server startup error")] + ServerStartupError, +} diff --git a/httpw/src/lib.rs b/httpw/src/lib.rs index e282251..b983a00 100644 --- a/httpw/src/lib.rs +++ b/httpw/src/lib.rs @@ -1,4 +1,5 @@ mod authentication; +pub mod errors; pub mod middlewares; pub mod server; pub mod viewmodels; diff --git a/httpw/src/middlewares/deserializer.rs b/httpw/src/middlewares/deserializer.rs index c9a2aeb..f92717e 100644 --- a/httpw/src/middlewares/deserializer.rs +++ b/httpw/src/middlewares/deserializer.rs @@ -4,7 +4,7 @@ use actix_web::{ HttpRequest, HttpResponse, }; -use crate::viewmodels::error::HttpErrorViewModel; +use crate::viewmodels::HttpErrorViewModel; pub fn handler() -> JsonConfig { JsonConfig::default().error_handler(|err: JsonPayloadError, _req: &HttpRequest| { @@ -12,7 +12,7 @@ pub fn handler() -> JsonConfig { format!("JSON error: {:?}", err), HttpResponse::BadRequest().json(HttpErrorViewModel { status_code: 400, - message: String::from("Wrong body format"), + message: String::from("wrong body format"), details: format!("{}", err), }), ) diff --git a/httpw/src/middlewares/not_found.rs b/httpw/src/middlewares/not_found.rs index 2cef5ce..34d2bc0 100644 --- a/httpw/src/middlewares/not_found.rs +++ b/httpw/src/middlewares/not_found.rs @@ -1,10 +1,10 @@ -use actix_web::{ - http::{header::ContentType, StatusCode}, - HttpResponse, -}; +use crate::viewmodels::HttpErrorViewModel; +use actix_web::{http::StatusCode, web, Responder}; -pub async fn not_found() -> HttpResponse { - HttpResponse::build(StatusCode::NOT_FOUND) - .content_type(ContentType::plaintext()) - .body("Not Found") +pub async fn not_found() -> impl Responder { + web::Json(HttpErrorViewModel { + status_code: StatusCode::NOT_FOUND.as_u16(), + message: "not found".to_owned(), + details: "the resource was not founded".to_owned(), + }) } diff --git a/httpw/src/server/server.rs b/httpw/src/server/server.rs index 41f4b60..d3da717 100644 --- a/httpw/src/server/server.rs +++ b/httpw/src/server/server.rs @@ -1,12 +1,11 @@ use std::sync::Arc; use super::types::AppConfig; -use crate::{authentication::token_validation, middlewares}; +use crate::{authentication::token_validation, errors::HttpServerError, middlewares}; use actix_web::{middleware as actix_middleware, web, App, HttpServer}; use actix_web_httpauth::middleware::HttpAuthentication; use auth::{dummy_middleware::DummyMiddleware, AuthMiddleware}; use env::AppConfig as AppEnv; -use errors::http_server::HttpServerError; use tracing::error; pub struct HttpwServerImpl { @@ -39,6 +38,12 @@ impl HttpwServerImpl { self } + pub fn auth_strategy(mut self, strategy: Arc) -> Self { + self.with_auth = true; + self.auth_strategy = strategy.clone(); + self + } + pub async fn start(&self) -> Result<(), HttpServerError> { HttpServer::new({ let with_auth = self.with_auth; @@ -51,17 +56,17 @@ impl HttpwServerImpl { .wrap(actix_middleware::Compress::default()) .wrap(middlewares::headers::config()) .wrap(middlewares::cors::config()) - .app_data(web::Data::new(auth_strategy.clone())) - .wrap(actix_middleware::Logger::default()); + .app_data(web::Data::>::new( + auth_strategy.clone(), + )); for svc in services_without_auth.clone() { app = app.configure(svc); } if with_auth { - //apply auth strategy - let auth_mid = HttpAuthentication::bearer(token_validation::validator); - // app = app.wrap(middlewares::headers::config()); + // let auth_mid = HttpAuthentication::bearer(token_validation::validator); + // app = app.wrap(actix_middleware::Logger::default()); } for svc in services_with_auth.clone() { @@ -69,6 +74,7 @@ impl HttpwServerImpl { } app.default_service(web::to(middlewares::not_found::not_found)) + .wrap(actix_middleware::Logger::default()) } }) .bind(&self.addr) @@ -77,13 +83,13 @@ impl HttpwServerImpl { error = e.to_string(), "error to binding the http server addr" ); - HttpServerError::ServerError {} + HttpServerError::PortBidingError {} })? .run() .await .map_err(|e| { error!(error = e.to_string(), "error to start http server"); - HttpServerError::ServerError {} + HttpServerError::ServerStartupError {} })?; Ok(()) diff --git a/httpw/src/server/types.rs b/httpw/src/server/types.rs index ae9a3fd..24173e2 100644 --- a/httpw/src/server/types.rs +++ b/httpw/src/server/types.rs @@ -1,3 +1,18 @@ use actix_web::web::ServiceConfig; pub type AppConfig = fn(cfg: &mut ServiceConfig); + +pub struct Auth { + permission: Option, +} + +pub struct Route { + path: String, + method: String, + auth: Option, +} + +pub struct RoutesConfig { + actix_cfg: fn(cfg: &mut ServiceConfig), + routes: Vec, +} diff --git a/httpw/src/viewmodels/error.rs b/httpw/src/viewmodels/error.rs index e43caf9..ea0b0a9 100644 --- a/httpw/src/viewmodels/error.rs +++ b/httpw/src/viewmodels/error.rs @@ -1,42 +1,8 @@ -use actix_web::{ - error::ResponseError, - http::{header::ContentType, StatusCode}, - HttpResponse, -}; use serde::Serialize; -use thiserror::Error; -#[derive(Serialize)] +#[derive(Default, Serialize)] pub struct HttpErrorViewModel { pub status_code: u16, pub message: String, pub details: String, } - -#[derive(Debug, Error)] -pub enum HttpError { - #[error("internal error")] - InternalError, - - #[error("bad request")] - BadRequest, - - #[error("timeout")] - Timeout, -} - -impl ResponseError for HttpError { - fn error_response(&self) -> HttpResponse { - HttpResponse::build(self.status_code()) - .insert_header(ContentType::json()) - .body(self.to_string()) - } - - fn status_code(&self) -> StatusCode { - match *self { - HttpError::InternalError => StatusCode::INTERNAL_SERVER_ERROR, - HttpError::BadRequest => StatusCode::BAD_REQUEST, - HttpError::Timeout => StatusCode::GATEWAY_TIMEOUT, - } - } -} diff --git a/httpw/src/viewmodels/mod.rs b/httpw/src/viewmodels/mod.rs index a91e735..1814597 100644 --- a/httpw/src/viewmodels/mod.rs +++ b/httpw/src/viewmodels/mod.rs @@ -1 +1,3 @@ -pub mod error; +mod error; + +pub use error::HttpErrorViewModel; From 8e7ce2ed738fe9e94057b5589475ca77e3ff36b5 Mon Sep 17 00:00:00 2001 From: ralvescosta Date: Thu, 9 Feb 2023 07:01:41 -0300 Subject: [PATCH 05/10] refactor: auth0 middleware --- httpw/src/server/mod.rs | 2 +- httpw/src/server/server.rs | 41 +++++++++++--------------------------- httpw/src/server/types.rs | 17 +--------------- 3 files changed, 14 insertions(+), 46 deletions(-) diff --git a/httpw/src/server/mod.rs b/httpw/src/server/mod.rs index bfe9246..68b623b 100644 --- a/httpw/src/server/mod.rs +++ b/httpw/src/server/mod.rs @@ -2,4 +2,4 @@ mod server; mod types; pub use server::HttpwServerImpl; -pub use types::AppConfig; +pub use types::RouteConfig; diff --git a/httpw/src/server/server.rs b/httpw/src/server/server.rs index d3da717..e990500 100644 --- a/httpw/src/server/server.rs +++ b/httpw/src/server/server.rs @@ -1,17 +1,18 @@ use std::sync::Arc; -use super::types::AppConfig; +use super::types::RouteConfig; use crate::{authentication::token_validation, errors::HttpServerError, middlewares}; -use actix_web::{middleware as actix_middleware, web, App, HttpServer}; -use actix_web_httpauth::middleware::HttpAuthentication; +use actix_web::{ + middleware as actix_middleware, + web::{self, ServiceConfig}, + App, FromRequest, Handler, HttpServer, Responder, +}; use auth::{dummy_middleware::DummyMiddleware, AuthMiddleware}; use env::AppConfig as AppEnv; use tracing::error; pub struct HttpwServerImpl { - services_without_auth: Vec, - services_with_auth: Vec, - with_auth: bool, + services: Vec, auth_strategy: Arc, addr: String, } @@ -19,9 +20,7 @@ pub struct HttpwServerImpl { impl HttpwServerImpl { pub fn new(cfg: &AppEnv) -> HttpwServerImpl { HttpwServerImpl { - services_without_auth: vec![], - services_with_auth: vec![], - with_auth: false, + services: vec![], auth_strategy: DummyMiddleware::new(), addr: cfg.app_addr(), } @@ -29,26 +28,19 @@ impl HttpwServerImpl { } impl HttpwServerImpl { - pub fn register(mut self, service: AppConfig) -> Self { - if self.with_auth { - self.services_with_auth.push(service); - } else { - self.services_without_auth.push(service); - } + pub fn register(mut self, service: RouteConfig) -> Self { + self.services.push(service); self } pub fn auth_strategy(mut self, strategy: Arc) -> Self { - self.with_auth = true; self.auth_strategy = strategy.clone(); self } pub async fn start(&self) -> Result<(), HttpServerError> { HttpServer::new({ - let with_auth = self.with_auth; - let services_without_auth = self.services_without_auth.to_vec(); - let services_with_auth = self.services_with_auth.to_vec(); + let services = self.services.to_vec(); let auth_strategy = self.auth_strategy.clone(); move || { @@ -60,16 +52,7 @@ impl HttpwServerImpl { auth_strategy.clone(), )); - for svc in services_without_auth.clone() { - app = app.configure(svc); - } - - if with_auth { - // let auth_mid = HttpAuthentication::bearer(token_validation::validator); - // app = app.wrap(actix_middleware::Logger::default()); - } - - for svc in services_with_auth.clone() { + for svc in services.clone() { app = app.configure(svc); } diff --git a/httpw/src/server/types.rs b/httpw/src/server/types.rs index 24173e2..aacf2f0 100644 --- a/httpw/src/server/types.rs +++ b/httpw/src/server/types.rs @@ -1,18 +1,3 @@ use actix_web::web::ServiceConfig; -pub type AppConfig = fn(cfg: &mut ServiceConfig); - -pub struct Auth { - permission: Option, -} - -pub struct Route { - path: String, - method: String, - auth: Option, -} - -pub struct RoutesConfig { - actix_cfg: fn(cfg: &mut ServiceConfig), - routes: Vec, -} +pub type RouteConfig = fn(cfg: &mut ServiceConfig); From 9d22aed1e89fda4fc900d5768e148d4086eb292d Mon Sep 17 00:00:00 2001 From: ralvescosta Date: Thu, 9 Feb 2023 12:28:40 -0300 Subject: [PATCH 06/10] refactor: auth0 middleware --- Cargo.lock | 221 +++++-------------- auth/Cargo.toml | 11 +- auth/src/dummy_middleware.rs | 28 --- auth/src/lib.rs | 5 - auth/src/{auth0_middleware.rs => old2.rs} | 0 httpw/Cargo.toml | 2 + httpw/src/authentication/token_validation.rs | 64 +++--- httpw/src/middlewares/jwt.rs | 62 ++++++ httpw/src/middlewares/mod.rs | 1 + httpw/src/server/server.rs | 24 +- httpw/src/viewmodels/error.rs | 10 +- 11 files changed, 160 insertions(+), 268 deletions(-) delete mode 100644 auth/src/dummy_middleware.rs rename auth/src/{auth0_middleware.rs => old2.rs} (100%) create mode 100644 httpw/src/middlewares/jwt.rs diff --git a/Cargo.lock b/Cargo.lock index 99e3f1c..dccc602 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -251,19 +251,6 @@ dependencies = [ "memchr", ] -[[package]] -name = "alcoholic_jwt" -version = "4091.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb8a1270ac7729a671a2d0f7d959e6572330d4da9f6b797165f6b08244019b9e" -dependencies = [ - "base64 0.13.1", - "openssl", - "serde", - "serde_derive", - "serde_json", -] - [[package]] name = "alloc-no-stdlib" version = "2.0.4" @@ -501,16 +488,6 @@ checksum = "debc29dde2e69f9e47506b525f639ed42300fc014a3e007832592448fa8e4599" [[package]] name = "auth" version = "0.1.0" -dependencies = [ - "alcoholic_jwt", - "async-trait", - "env", - "opentelemetry", - "reqwest", - "serde", - "tokio", - "tracing", -] [[package]] name = "autocfg" @@ -1401,21 +1378,6 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" -[[package]] -name = "foreign-types" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" -dependencies = [ - "foreign-types-shared", -] - -[[package]] -name = "foreign-types-shared" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" - [[package]] name = "form_urlencoded" version = "1.1.0" @@ -1703,8 +1665,10 @@ dependencies = [ "actix-web-httpauth", "auth", "env", + "jsonwebtoken", "opentelemetry", "serde", + "serde_json", "thiserror", "tracing", ] @@ -1760,19 +1724,6 @@ dependencies = [ "tokio-io-timeout", ] -[[package]] -name = "hyper-tls" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" -dependencies = [ - "bytes", - "hyper", - "native-tls", - "tokio", - "tokio-native-tls", -] - [[package]] name = "iana-time-zone" version = "0.1.53" @@ -1836,12 +1787,6 @@ dependencies = [ "cfg-if", ] -[[package]] -name = "ipnet" -version = "2.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30e22bd8629359895450b59ea7a776c850561b96a3b1d31321c1949d9e6c9146" - [[package]] name = "itertools" version = "0.10.5" @@ -1875,6 +1820,20 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "jsonwebtoken" +version = "8.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09f4f04699947111ec1733e71778d763555737579e44b85844cae8e1940a1828" +dependencies = [ + "base64 0.13.1", + "pem", + "ring", + "serde", + "serde_json", + "simple_asn1", +] + [[package]] name = "language-tags" version = "0.3.2" @@ -2115,24 +2074,6 @@ version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e5ce46fe64a9d73be07dcbe690a38ce1b293be448fd8ce1e6c1b8062c9f72c6a" -[[package]] -name = "native-tls" -version = "0.2.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07226173c32f2926027b63cce4bcd8076c3552846cbe7925f3aaffeac0a3b92e" -dependencies = [ - "lazy_static", - "libc", - "log", - "openssl", - "openssl-probe", - "openssl-sys", - "schannel", - "security-framework", - "security-framework-sys", - "tempfile", -] - [[package]] name = "nom" version = "7.1.3" @@ -2159,6 +2100,17 @@ dependencies = [ "winapi", ] +[[package]] +name = "num-bigint" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f93ab6289c7b344a8a9f60f88d80aa20032336fe78da341afc91c8a2341fc75f" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + [[package]] name = "num-integer" version = "0.1.45" @@ -2194,32 +2146,6 @@ version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6f61fba1741ea2b3d6a1e3178721804bb716a68a6aeba1149b5d52e3d464ea66" -[[package]] -name = "openssl" -version = "0.10.45" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b102428fd03bc5edf97f62620f7298614c45cedf287c271e7ed450bbaf83f2e1" -dependencies = [ - "bitflags", - "cfg-if", - "foreign-types", - "libc", - "once_cell", - "openssl-macros", - "openssl-sys", -] - -[[package]] -name = "openssl-macros" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b501e44f11665960c7e7fcf062c7d96a14ade4aa98116c004b2e37b5be7d736c" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "openssl-probe" version = "0.1.5" @@ -2403,6 +2329,15 @@ version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d01a5bd0424d00070b0098dd17ebca6f961a959dead1dbcbbbc1d1cd8d3deeba" +[[package]] +name = "pem" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8835c273a76a90455d7344889b0964598e3316e2a79ede8e36f16bdcf2228b8" +dependencies = [ + "base64 0.13.1", +] + [[package]] name = "percent-encoding" version = "2.2.0" @@ -2736,43 +2671,6 @@ dependencies = [ "winapi", ] -[[package]] -name = "reqwest" -version = "0.11.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21eed90ec8570952d53b772ecf8f206aa1ec9a3d76b2521c56c42973f2d91ee9" -dependencies = [ - "base64 0.21.0", - "bytes", - "encoding_rs", - "futures-core", - "futures-util", - "h2", - "http", - "http-body", - "hyper", - "hyper-tls", - "ipnet", - "js-sys", - "log", - "mime", - "native-tls", - "once_cell", - "percent-encoding", - "pin-project-lite", - "serde", - "serde_json", - "serde_urlencoded", - "tokio", - "tokio-native-tls", - "tower-service", - "url", - "wasm-bindgen", - "wasm-bindgen-futures", - "web-sys", - "winreg", -] - [[package]] name = "retain_mut" version = "0.1.9" @@ -2970,9 +2868,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.91" +version = "1.0.93" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "877c235533714907a8c2464236f5c4b2a17262ef1bd71f38f35ea592c8da6883" +checksum = "cad406b69c91885b5107daf2c29572f6c8cdb3c66826821e286c533490c0bc76" dependencies = [ "itoa", "ryu", @@ -3031,6 +2929,18 @@ dependencies = [ "libc", ] +[[package]] +name = "simple_asn1" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adc4e5204eb1910f40f9cfa375f6f05b68c3abac4b6fd879c8ff5e7ae8a0a085" +dependencies = [ + "num-bigint", + "num-traits", + "thiserror", + "time", +] + [[package]] name = "siphasher" version = "0.3.10" @@ -3274,16 +3184,6 @@ dependencies = [ "syn", ] -[[package]] -name = "tokio-native-tls" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7d995660bd2b7f8c1568414c1126076c13fbb725c40112dc0120b78eb9b717b" -dependencies = [ - "native-tls", - "tokio", -] - [[package]] name = "tokio-postgres" version = "0.7.7" @@ -3685,18 +3585,6 @@ dependencies = [ "wasm-bindgen-shared", ] -[[package]] -name = "wasm-bindgen-futures" -version = "0.4.33" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23639446165ca5a5de86ae1d8896b737ae80319560fbaa4c2887b7da6e7ebd7d" -dependencies = [ - "cfg-if", - "js-sys", - "wasm-bindgen", - "web-sys", -] - [[package]] name = "wasm-bindgen-macro" version = "0.2.83" @@ -3854,15 +3742,6 @@ version = "0.42.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "447660ad36a13288b1db4d4248e857b510e8c3a225c822ba4fb748c0aafecffd" -[[package]] -name = "winreg" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80d0f4e272c85def139476380b12f9ac60926689dd2e01d4923222f40580869d" -dependencies = [ - "winapi", -] - [[package]] name = "xmlparser" version = "0.13.5" diff --git a/auth/Cargo.toml b/auth/Cargo.toml index 7d9ea02..f94eba4 100644 --- a/auth/Cargo.toml +++ b/auth/Cargo.toml @@ -3,13 +3,4 @@ name = "auth" version = "0.1.0" edition = "2021" -[dependencies] -env = { path = "../env" } - -serde = { version = "1.0.152", features = ["derive"] } -alcoholic_jwt = { version = "4091.0.0" } -reqwest = { version = "0.11.14", features = ["json"] } -async-trait = { version = "0.1.64" } -tracing = { version = "0.1.37" } -opentelemetry = { version = "0.18.0" } -tokio = { version = "1.25.0", features = ["sync"] } +[dependencies] \ No newline at end of file diff --git a/auth/src/dummy_middleware.rs b/auth/src/dummy_middleware.rs deleted file mode 100644 index 54f3cef..0000000 --- a/auth/src/dummy_middleware.rs +++ /dev/null @@ -1,28 +0,0 @@ -use crate::{AuthMiddleware, Scopes}; -use async_trait::async_trait; -use opentelemetry::Context; -use std::sync::Arc; - -pub struct DummyMiddleware; - -impl DummyMiddleware { - pub fn new() -> Arc { - Arc::new(DummyMiddleware {}) - } -} - -#[async_trait] -impl AuthMiddleware for DummyMiddleware { - async fn authenticate(&self, _ctx: &Context, _token: &str) -> Result { - Ok(true) - } - - async fn authorize( - &self, - _ctx: &Context, - _token: &str, - _required_scope: Scopes, - ) -> Result { - Ok(true) - } -} diff --git a/auth/src/lib.rs b/auth/src/lib.rs index 1925fba..2643098 100644 --- a/auth/src/lib.rs +++ b/auth/src/lib.rs @@ -1,8 +1,3 @@ -pub mod auth0_middleware; mod defs; -pub mod dummy_middleware; -mod old; -mod types; pub use defs::{PlatformScopes, Scopes, ThingsScopes, UsersScopes}; -pub use types::AuthMiddleware; diff --git a/auth/src/auth0_middleware.rs b/auth/src/old2.rs similarity index 100% rename from auth/src/auth0_middleware.rs rename to auth/src/old2.rs diff --git a/httpw/Cargo.toml b/httpw/Cargo.toml index eb5fc4f..d33a434 100644 --- a/httpw/Cargo.toml +++ b/httpw/Cargo.toml @@ -12,5 +12,7 @@ actix-web = { version = "4.3.0" } actix-web-httpauth = "0.8.0" actix-cors = { version = "0.6.3" } serde = { version = "1.0.152", features = ["derive"] } +serde_json = { version = "1.0.93" } tracing = { version = "0.1.37" } opentelemetry = { version = "0.18.0" } +jsonwebtoken = { version = "8.2.0" } diff --git a/httpw/src/authentication/token_validation.rs b/httpw/src/authentication/token_validation.rs index a62199d..de94bb8 100644 --- a/httpw/src/authentication/token_validation.rs +++ b/httpw/src/authentication/token_validation.rs @@ -1,36 +1,36 @@ -use std::sync::Arc; +// use std::sync::Arc; -use actix_web::{dev::ServiceRequest, Error}; -use actix_web_httpauth::extractors::bearer::{BearerAuth, Config}; -use actix_web_httpauth::extractors::AuthenticationError; -use auth::AuthMiddleware; -use opentelemetry::Context; +// use actix_web::{dev::ServiceRequest, Error}; +// use actix_web_httpauth::extractors::bearer::{BearerAuth, Config}; +// use actix_web_httpauth::extractors::AuthenticationError; +// use auth::AuthMiddleware; +// use opentelemetry::Context; -pub async fn validator( - req: ServiceRequest, - credentials: BearerAuth, -) -> Result { - let auth_mid = req - .app_data::>() - .map(|data| data.clone()) - .unwrap(); +// pub async fn validator( +// req: ServiceRequest, +// credentials: BearerAuth, +// ) -> Result { +// let auth_mid = req +// .app_data::>() +// .map(|data| data.clone()) +// .unwrap(); - let config = req - .app_data::() - .map(|data| data.clone()) - .unwrap_or_else(Default::default); +// let config = req +// .app_data::() +// .map(|data| data.clone()) +// .unwrap_or_else(Default::default); - match auth_mid - .authenticate(&Context::new(), credentials.token()) - .await - { - Ok(res) => { - if res == true { - Ok(req) - } else { - Err((AuthenticationError::from(config).into(), req)) - } - } - Err(_) => Err((AuthenticationError::from(config).into(), req)), - } -} +// match auth_mid +// .authenticate(&Context::new(), credentials.token()) +// .await +// { +// Ok(res) => { +// if res == true { +// Ok(req) +// } else { +// Err((AuthenticationError::from(config).into(), req)) +// } +// } +// Err(_) => Err((AuthenticationError::from(config).into(), req)), +// } +// } diff --git a/httpw/src/middlewares/jwt.rs b/httpw/src/middlewares/jwt.rs new file mode 100644 index 0000000..6ab4765 --- /dev/null +++ b/httpw/src/middlewares/jwt.rs @@ -0,0 +1,62 @@ +use crate::viewmodels::HttpErrorViewModel; +use actix_web::error::ErrorUnauthorized; +use actix_web::{dev::Payload, Error as ActixWebError}; +use actix_web::{http, FromRequest, HttpMessage, HttpRequest}; +use env::AppConfig; +use jsonwebtoken::{decode, DecodingKey, Validation}; +use serde::{Deserialize, Serialize}; +use std::future::{ready, Ready}; + +#[derive(Debug, Serialize, Deserialize)] +pub struct TokenClaims { + pub sub: String, + pub iat: usize, + pub exp: usize, +} + +pub struct JwtAuthorizationMiddleware { + pub user_id: String, +} + +impl FromRequest for JwtAuthorizationMiddleware { + type Error = ActixWebError; + type Future = Ready>; + fn from_request(req: &HttpRequest, _: &mut Payload) -> Self::Future { + // let cfg = req.app_data::().unwrap(); + + let token = req + .headers() + .get(http::header::AUTHORIZATION) + .map(|h| h.to_str().unwrap().split_at(7).1.to_string()); + + if token.is_none() { + let json_error = HttpErrorViewModel { + status_code: http::StatusCode::UNAUTHORIZED.as_u16(), + message: "unauthorized".to_owned(), + details: "You are not logged in, please provide token".to_string(), + }; + return ready(Err(ErrorUnauthorized(json_error))); + } + + let claims = match decode::( + &token.unwrap(), + &DecodingKey::from_secret("".as_ref()), + &Validation::default(), + ) { + Ok(c) => c.claims, + Err(_) => { + let json_error = HttpErrorViewModel { + status_code: http::StatusCode::UNAUTHORIZED.as_u16(), + message: "unauthorized".to_owned(), + details: "invalid token".to_string(), + }; + return ready(Err(ErrorUnauthorized(json_error))); + } + }; + + let user_id = claims.sub; + req.extensions_mut().insert::(user_id.to_owned()); + + ready(Ok(JwtAuthorizationMiddleware { user_id })) + } +} diff --git a/httpw/src/middlewares/mod.rs b/httpw/src/middlewares/mod.rs index 0e44b50..9c2abfa 100644 --- a/httpw/src/middlewares/mod.rs +++ b/httpw/src/middlewares/mod.rs @@ -1,4 +1,5 @@ pub mod cors; pub mod deserializer; pub mod headers; +pub mod jwt; pub mod not_found; diff --git a/httpw/src/server/server.rs b/httpw/src/server/server.rs index e990500..4276b27 100644 --- a/httpw/src/server/server.rs +++ b/httpw/src/server/server.rs @@ -1,19 +1,11 @@ -use std::sync::Arc; - use super::types::RouteConfig; -use crate::{authentication::token_validation, errors::HttpServerError, middlewares}; -use actix_web::{ - middleware as actix_middleware, - web::{self, ServiceConfig}, - App, FromRequest, Handler, HttpServer, Responder, -}; -use auth::{dummy_middleware::DummyMiddleware, AuthMiddleware}; +use crate::{errors::HttpServerError, middlewares}; +use actix_web::{middleware as actix_middleware, web, App, HttpServer}; use env::AppConfig as AppEnv; use tracing::error; pub struct HttpwServerImpl { services: Vec, - auth_strategy: Arc, addr: String, } @@ -21,7 +13,6 @@ impl HttpwServerImpl { pub fn new(cfg: &AppEnv) -> HttpwServerImpl { HttpwServerImpl { services: vec![], - auth_strategy: DummyMiddleware::new(), addr: cfg.app_addr(), } } @@ -33,24 +24,15 @@ impl HttpwServerImpl { self } - pub fn auth_strategy(mut self, strategy: Arc) -> Self { - self.auth_strategy = strategy.clone(); - self - } - pub async fn start(&self) -> Result<(), HttpServerError> { HttpServer::new({ let services = self.services.to_vec(); - let auth_strategy = self.auth_strategy.clone(); move || { let mut app = App::new() .wrap(actix_middleware::Compress::default()) .wrap(middlewares::headers::config()) - .wrap(middlewares::cors::config()) - .app_data(web::Data::>::new( - auth_strategy.clone(), - )); + .wrap(middlewares::cors::config()); for svc in services.clone() { app = app.configure(svc); diff --git a/httpw/src/viewmodels/error.rs b/httpw/src/viewmodels/error.rs index ea0b0a9..2f61e33 100644 --- a/httpw/src/viewmodels/error.rs +++ b/httpw/src/viewmodels/error.rs @@ -1,8 +1,16 @@ +use std::fmt; + use serde::Serialize; -#[derive(Default, Serialize)] +#[derive(Debug, Default, Serialize)] pub struct HttpErrorViewModel { pub status_code: u16, pub message: String, pub details: String, } + +impl fmt::Display for HttpErrorViewModel { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", serde_json::to_string(&self).unwrap()) + } +} From c832ec839bb15345e8973af5978cd7eb25a705cd Mon Sep 17 00:00:00 2001 From: ralvescosta Date: Fri, 10 Feb 2023 06:59:18 -0300 Subject: [PATCH 07/10] feat: auth extractor and autho token manager --- Cargo.lock | 232 ++++++++++++++----- auth/Cargo.toml | 10 +- auth/src/{old2.rs => jwt_manager/auth0.rs} | 82 ++++--- auth/src/jwt_manager/manager.rs | 19 ++ auth/src/jwt_manager/mod.rs | 4 + auth/src/lib.rs | 2 + auth/src/old.rs | 39 ---- auth/src/types.rs | 13 -- httpw/Cargo.toml | 4 +- httpw/src/authentication/mod.rs | 1 - httpw/src/authentication/token_validation.rs | 36 --- httpw/src/extractors/jwt.rs | 63 +++++ httpw/src/extractors/mod.rs | 3 + httpw/src/lib.rs | 2 +- httpw/src/middlewares/jwt.rs | 62 ----- httpw/src/middlewares/mod.rs | 1 - httpw/src/server/server.rs | 20 +- 17 files changed, 337 insertions(+), 256 deletions(-) rename auth/src/{old2.rs => jwt_manager/auth0.rs} (69%) create mode 100644 auth/src/jwt_manager/manager.rs create mode 100644 auth/src/jwt_manager/mod.rs delete mode 100644 auth/src/old.rs delete mode 100644 httpw/src/authentication/mod.rs delete mode 100644 httpw/src/authentication/token_validation.rs create mode 100644 httpw/src/extractors/jwt.rs create mode 100644 httpw/src/extractors/mod.rs delete mode 100644 httpw/src/middlewares/jwt.rs diff --git a/Cargo.lock b/Cargo.lock index dccc602..8c4d85d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -198,21 +198,6 @@ dependencies = [ "syn", ] -[[package]] -name = "actix-web-httpauth" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6dda62cf04bc3a9ad2ea8f314f721951cfdb4cdacec4e984d20e77c7bb170991" -dependencies = [ - "actix-utils", - "actix-web", - "base64 0.13.1", - "futures-core", - "futures-util", - "log", - "pin-project-lite", -] - [[package]] name = "adler" version = "1.0.2" @@ -251,6 +236,19 @@ dependencies = [ "memchr", ] +[[package]] +name = "alcoholic_jwt" +version = "4091.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb8a1270ac7729a671a2d0f7d959e6572330d4da9f6b797165f6b08244019b9e" +dependencies = [ + "base64 0.13.1", + "openssl", + "serde", + "serde_derive", + "serde_json", +] + [[package]] name = "alloc-no-stdlib" version = "2.0.4" @@ -488,6 +486,16 @@ checksum = "debc29dde2e69f9e47506b525f639ed42300fc014a3e007832592448fa8e4599" [[package]] name = "auth" version = "0.1.0" +dependencies = [ + "alcoholic_jwt", + "async-trait", + "opentelemetry", + "reqwest", + "serde", + "serde_json", + "tokio", + "tracing", +] [[package]] name = "autocfg" @@ -1378,6 +1386,21 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +[[package]] +name = "foreign-types" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +dependencies = [ + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-shared" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" + [[package]] name = "form_urlencoded" version = "1.1.0" @@ -1662,10 +1685,8 @@ version = "0.1.0" dependencies = [ "actix-cors", "actix-web", - "actix-web-httpauth", "auth", "env", - "jsonwebtoken", "opentelemetry", "serde", "serde_json", @@ -1724,6 +1745,19 @@ dependencies = [ "tokio-io-timeout", ] +[[package]] +name = "hyper-tls" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" +dependencies = [ + "bytes", + "hyper", + "native-tls", + "tokio", + "tokio-native-tls", +] + [[package]] name = "iana-time-zone" version = "0.1.53" @@ -1787,6 +1821,12 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "ipnet" +version = "2.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30e22bd8629359895450b59ea7a776c850561b96a3b1d31321c1949d9e6c9146" + [[package]] name = "itertools" version = "0.10.5" @@ -1820,20 +1860,6 @@ dependencies = [ "wasm-bindgen", ] -[[package]] -name = "jsonwebtoken" -version = "8.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09f4f04699947111ec1733e71778d763555737579e44b85844cae8e1940a1828" -dependencies = [ - "base64 0.13.1", - "pem", - "ring", - "serde", - "serde_json", - "simple_asn1", -] - [[package]] name = "language-tags" version = "0.3.2" @@ -2074,6 +2100,24 @@ version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e5ce46fe64a9d73be07dcbe690a38ce1b293be448fd8ce1e6c1b8062c9f72c6a" +[[package]] +name = "native-tls" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07226173c32f2926027b63cce4bcd8076c3552846cbe7925f3aaffeac0a3b92e" +dependencies = [ + "lazy_static", + "libc", + "log", + "openssl", + "openssl-probe", + "openssl-sys", + "schannel", + "security-framework", + "security-framework-sys", + "tempfile", +] + [[package]] name = "nom" version = "7.1.3" @@ -2100,17 +2144,6 @@ dependencies = [ "winapi", ] -[[package]] -name = "num-bigint" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f93ab6289c7b344a8a9f60f88d80aa20032336fe78da341afc91c8a2341fc75f" -dependencies = [ - "autocfg", - "num-integer", - "num-traits", -] - [[package]] name = "num-integer" version = "0.1.45" @@ -2146,6 +2179,32 @@ version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6f61fba1741ea2b3d6a1e3178721804bb716a68a6aeba1149b5d52e3d464ea66" +[[package]] +name = "openssl" +version = "0.10.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b102428fd03bc5edf97f62620f7298614c45cedf287c271e7ed450bbaf83f2e1" +dependencies = [ + "bitflags", + "cfg-if", + "foreign-types", + "libc", + "once_cell", + "openssl-macros", + "openssl-sys", +] + +[[package]] +name = "openssl-macros" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b501e44f11665960c7e7fcf062c7d96a14ade4aa98116c004b2e37b5be7d736c" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "openssl-probe" version = "0.1.5" @@ -2329,15 +2388,6 @@ version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d01a5bd0424d00070b0098dd17ebca6f961a959dead1dbcbbbc1d1cd8d3deeba" -[[package]] -name = "pem" -version = "1.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8835c273a76a90455d7344889b0964598e3316e2a79ede8e36f16bdcf2228b8" -dependencies = [ - "base64 0.13.1", -] - [[package]] name = "percent-encoding" version = "2.2.0" @@ -2671,6 +2721,43 @@ dependencies = [ "winapi", ] +[[package]] +name = "reqwest" +version = "0.11.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21eed90ec8570952d53b772ecf8f206aa1ec9a3d76b2521c56c42973f2d91ee9" +dependencies = [ + "base64 0.21.0", + "bytes", + "encoding_rs", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "hyper", + "hyper-tls", + "ipnet", + "js-sys", + "log", + "mime", + "native-tls", + "once_cell", + "percent-encoding", + "pin-project-lite", + "serde", + "serde_json", + "serde_urlencoded", + "tokio", + "tokio-native-tls", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "winreg", +] + [[package]] name = "retain_mut" version = "0.1.9" @@ -2929,18 +3016,6 @@ dependencies = [ "libc", ] -[[package]] -name = "simple_asn1" -version = "0.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "adc4e5204eb1910f40f9cfa375f6f05b68c3abac4b6fd879c8ff5e7ae8a0a085" -dependencies = [ - "num-bigint", - "num-traits", - "thiserror", - "time", -] - [[package]] name = "siphasher" version = "0.3.10" @@ -3184,6 +3259,16 @@ dependencies = [ "syn", ] +[[package]] +name = "tokio-native-tls" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" +dependencies = [ + "native-tls", + "tokio", +] + [[package]] name = "tokio-postgres" version = "0.7.7" @@ -3585,6 +3670,18 @@ dependencies = [ "wasm-bindgen-shared", ] +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23639446165ca5a5de86ae1d8896b737ae80319560fbaa4c2887b7da6e7ebd7d" +dependencies = [ + "cfg-if", + "js-sys", + "wasm-bindgen", + "web-sys", +] + [[package]] name = "wasm-bindgen-macro" version = "0.2.83" @@ -3742,6 +3839,15 @@ version = "0.42.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "447660ad36a13288b1db4d4248e857b510e8c3a225c822ba4fb748c0aafecffd" +[[package]] +name = "winreg" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80d0f4e272c85def139476380b12f9ac60926689dd2e01d4923222f40580869d" +dependencies = [ + "winapi", +] + [[package]] name = "xmlparser" version = "0.13.5" diff --git a/auth/Cargo.toml b/auth/Cargo.toml index f94eba4..372e62d 100644 --- a/auth/Cargo.toml +++ b/auth/Cargo.toml @@ -3,4 +3,12 @@ name = "auth" version = "0.1.0" edition = "2021" -[dependencies] \ No newline at end of file +[dependencies] +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"] } \ No newline at end of file diff --git a/auth/src/old2.rs b/auth/src/jwt_manager/auth0.rs similarity index 69% rename from auth/src/old2.rs rename to auth/src/jwt_manager/auth0.rs index 816bde0..e5108bd 100644 --- a/auth/src/old2.rs +++ b/auth/src/jwt_manager/auth0.rs @@ -1,7 +1,6 @@ -use crate::{defs::Scopes, types::AuthMiddleware}; +use super::{JwtManager, TokenClaims}; use alcoholic_jwt::{token_kid, validate, Validation, JWKS}; use async_trait::async_trait; -use env::{Configs, Empty}; use opentelemetry::{ global::{self, BoxedSpan, BoxedTracer}, trace::{Span, Status, Tracer}, @@ -9,32 +8,33 @@ use opentelemetry::{ }; use std::{ borrow::Cow, + sync::Arc, time::{Duration, SystemTime}, }; use tokio::sync::Mutex; use tracing::error; -pub struct Auth0Middleware { +pub struct Auth0JwtManager { jwks: Mutex>, jwks_retrieved_at: SystemTime, authority: String, tracer: BoxedTracer, } -impl Auth0Middleware { - pub fn new(cfg: &Configs) -> Auth0Middleware { - Auth0Middleware { +impl Auth0JwtManager { + pub fn new() -> Arc { + Arc::new(Auth0JwtManager { jwks: Mutex::new(None), jwks_retrieved_at: SystemTime::now(), authority: String::new(), //get from the config tracer: global::tracer("auth0_middleware"), - } + }) } } #[async_trait] -impl AuthMiddleware for Auth0Middleware { - async fn authenticate(&self, ctx: &Context, token: &str) -> Result { +impl JwtManager for Auth0JwtManager { + async fn verify(&self, ctx: &Context, token: &str) -> Result { let mut span = self.tracer.start_with_context("authenticate", ctx); let jwks = self.retrieve_jwks(&mut span).await?; @@ -70,27 +70,40 @@ impl AuthMiddleware for Auth0Middleware { Validation::SubjectPresent, ]; - let jwk = jwks.find(&kid).expect("Specified key not found in set"); - let res = validate(token, jwk, validations); + let jwk = match jwks.find(&kid) { + Some(jwk) => Ok(jwk), + _ => { + error!("specified jwk key was not founded in set"); - Ok(res.is_ok()) - } + span.set_status(Status::Error { + description: Cow::from("specified jwk key was not founded in set"), + }); - async fn authorize( - &self, - ctx: &Context, - token: &str, - required_scope: Scopes, - ) -> Result { - let mut span = self.tracer.start_with_context("authenticate", ctx); + Err(()) + } + }?; - let jwks = self.retrieve_jwks(&mut span).await?; + let claims = match validate(token, jwk, validations) { + Ok(res) => Ok(res.claims), + Err(err) => { + error!(error = err.to_string(), "invalid jwt token"); - Ok(true) + span.record_error(&err); + span.set_status(Status::Error { + description: Cow::from("invalid jwt token"), + }); + + Err(()) + } + }?; + + span.set_status(Status::Ok); + + Ok(TokenClaims::default()) } } -impl Auth0Middleware { +impl Auth0JwtManager { async fn retrieve_jwks(&self, span: &mut BoxedSpan) -> Result { let mut jwks = self.jwks.lock().await; @@ -127,19 +140,20 @@ impl Auth0Middleware { } async fn get_jwks(&self, span: &mut BoxedSpan) -> Result { - let res = match reqwest::get("").await { - Err(err) => { - error!(error = err.to_string(), "error to get jwks from auth0 api"); + 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"), - }); + span.record_error(&err); + span.set_status(Status::Error { + description: Cow::from("error to get jwks from auth0 api"), + }); - Err(()) - } - Ok(r) => Ok(r), - }?; + Err(()) + } + Ok(r) => Ok(r), + }?; let val = match res.json::().await { Err(err) => { diff --git a/auth/src/jwt_manager/manager.rs b/auth/src/jwt_manager/manager.rs new file mode 100644 index 0000000..8f93f34 --- /dev/null +++ b/auth/src/jwt_manager/manager.rs @@ -0,0 +1,19 @@ +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, + pub iat: usize, + pub exp: usize, + pub scope: String, + pub permissions: Vec, +} + +#[async_trait] +pub trait JwtManager { + async fn verify(&self, ctx: &Context, token: &str) -> Result; +} diff --git a/auth/src/jwt_manager/mod.rs b/auth/src/jwt_manager/mod.rs new file mode 100644 index 0000000..bfd2527 --- /dev/null +++ b/auth/src/jwt_manager/mod.rs @@ -0,0 +1,4 @@ +pub mod auth0; +mod manager; + +pub use manager::{JwtManager, TokenClaims}; diff --git a/auth/src/lib.rs b/auth/src/lib.rs index 2643098..4a95d83 100644 --- a/auth/src/lib.rs +++ b/auth/src/lib.rs @@ -1,3 +1,5 @@ mod defs; +pub mod jwt_manager; +mod types; pub use defs::{PlatformScopes, Scopes, ThingsScopes, UsersScopes}; diff --git a/auth/src/old.rs b/auth/src/old.rs deleted file mode 100644 index 90fb1be..0000000 --- a/auth/src/old.rs +++ /dev/null @@ -1,39 +0,0 @@ -use alcoholic_jwt::{token_kid, validate, Validation, JWKS}; -use serde::{Deserialize, Serialize}; -use std::error::Error; - -#[derive(Debug, Serialize, Deserialize)] -struct Claims { - sub: String, - company: String, - exp: usize, -} - -pub async fn validate_token(token: &str) -> Result { - let authority = std::env::var("AUTHORITY").expect("AUTHORITY must be set"); - - let jwks = fetch_jwks(&format!( - "{}{}", - authority.as_str(), - ".well-known/jwks.json" - )) - .await - .expect("failed to fetch jwks"); - - let kid = match token_kid(&token) { - Ok(res) => res.expect("failed to decode kid"), - Err(_) => return Err(()), - }; - - let validations = vec![Validation::Issuer(authority), Validation::SubjectPresent]; - let jwk = jwks.find(&kid).expect("Specified key not found in set"); - let res = validate(token, jwk, validations); - - Ok(res.is_ok()) -} - -async fn fetch_jwks(uri: &str) -> Result> { - let res = reqwest::get(uri).await?; - let val = res.json::().await?; - return Ok(val); -} diff --git a/auth/src/types.rs b/auth/src/types.rs index cbf8ea9..8b13789 100644 --- a/auth/src/types.rs +++ b/auth/src/types.rs @@ -1,14 +1 @@ -use crate::defs::Scopes; -use async_trait::async_trait; -use opentelemetry::Context; -#[async_trait] -pub trait AuthMiddleware { - async fn authenticate(&self, ctx: &Context, token: &str) -> Result; - async fn authorize( - &self, - ctx: &Context, - token: &str, - required_scope: Scopes, - ) -> Result; -} diff --git a/httpw/Cargo.toml b/httpw/Cargo.toml index d33a434..3e94717 100644 --- a/httpw/Cargo.toml +++ b/httpw/Cargo.toml @@ -9,10 +9,8 @@ auth = { path = "../auth" } thiserror = { version = "1.0.37" } actix-web = { version = "4.3.0" } -actix-web-httpauth = "0.8.0" actix-cors = { version = "0.6.3" } serde = { version = "1.0.152", features = ["derive"] } serde_json = { version = "1.0.93" } tracing = { version = "0.1.37" } -opentelemetry = { version = "0.18.0" } -jsonwebtoken = { version = "8.2.0" } +opentelemetry = { version = "0.18.0" } \ No newline at end of file diff --git a/httpw/src/authentication/mod.rs b/httpw/src/authentication/mod.rs deleted file mode 100644 index d7cdd4e..0000000 --- a/httpw/src/authentication/mod.rs +++ /dev/null @@ -1 +0,0 @@ -pub mod token_validation; diff --git a/httpw/src/authentication/token_validation.rs b/httpw/src/authentication/token_validation.rs deleted file mode 100644 index de94bb8..0000000 --- a/httpw/src/authentication/token_validation.rs +++ /dev/null @@ -1,36 +0,0 @@ -// use std::sync::Arc; - -// use actix_web::{dev::ServiceRequest, Error}; -// use actix_web_httpauth::extractors::bearer::{BearerAuth, Config}; -// use actix_web_httpauth::extractors::AuthenticationError; -// use auth::AuthMiddleware; -// use opentelemetry::Context; - -// pub async fn validator( -// req: ServiceRequest, -// credentials: BearerAuth, -// ) -> Result { -// let auth_mid = req -// .app_data::>() -// .map(|data| data.clone()) -// .unwrap(); - -// let config = req -// .app_data::() -// .map(|data| data.clone()) -// .unwrap_or_else(Default::default); - -// match auth_mid -// .authenticate(&Context::new(), credentials.token()) -// .await -// { -// Ok(res) => { -// if res == true { -// Ok(req) -// } else { -// Err((AuthenticationError::from(config).into(), req)) -// } -// } -// Err(_) => Err((AuthenticationError::from(config).into(), req)), -// } -// } diff --git a/httpw/src/extractors/jwt.rs b/httpw/src/extractors/jwt.rs new file mode 100644 index 0000000..906dda0 --- /dev/null +++ b/httpw/src/extractors/jwt.rs @@ -0,0 +1,63 @@ +use crate::viewmodels::HttpErrorViewModel; +use actix_web::error::ErrorUnauthorized; +use actix_web::{dev::Payload, Error as ActixWebError}; +use actix_web::{http, FromRequest, HttpRequest}; +use auth::jwt_manager::JwtManager; +use env::AppConfig; +use opentelemetry::Context; +use serde::{Deserialize, Serialize}; +use std::future::{Future}; +use std::pin::Pin; +use std::sync::Arc; + +#[derive(Debug, Serialize, Deserialize)] +pub struct TokenClaims { + pub sub: String, + pub iat: usize, + pub exp: usize, +} + +pub struct JwtAuthenticateExtractor { + pub user_id: String, +} + +impl FromRequest for JwtAuthenticateExtractor { + type Error = ActixWebError; + type Future = Pin>>>; + + fn from_request(req: &HttpRequest, _: &mut Payload) -> Self::Future { + let token = req + .headers() + .get(http::header::AUTHORIZATION) + .map(|h| h.to_str().unwrap().split_at(7).1.to_string()); + + let jwt_manager = req.app_data::>().unwrap().clone(); + + Box::pin(async move { + let Some(token) = token else { + let json_error = HttpErrorViewModel { + status_code: http::StatusCode::UNAUTHORIZED.as_u16(), + message: "unauthorized".to_owned(), + details: "You are not logged in, please provide token".to_string(), + }; + + return Err(ErrorUnauthorized(json_error)); + }; + + let Ok(claims) = jwt_manager.verify(&Context::new(), &token).await else { + let json_error = HttpErrorViewModel { + status_code: http::StatusCode::UNAUTHORIZED.as_u16(), + message: "unauthorized".to_owned(), + details: "You are not logged in, please provide token".to_string(), + }; + + return Err(ErrorUnauthorized(json_error)); + }; + + // let user_id = claims.sub; + // req.extensions_mut().insert::(user_id.to_owned()); + + Ok(JwtAuthenticateExtractor { user_id: String::new() }) + }) + } +} diff --git a/httpw/src/extractors/mod.rs b/httpw/src/extractors/mod.rs new file mode 100644 index 0000000..f700c00 --- /dev/null +++ b/httpw/src/extractors/mod.rs @@ -0,0 +1,3 @@ +mod jwt; + +pub use jwt::{JwtAuthenticateExtractor, TokenClaims}; diff --git a/httpw/src/lib.rs b/httpw/src/lib.rs index b983a00..7bac24b 100644 --- a/httpw/src/lib.rs +++ b/httpw/src/lib.rs @@ -1,5 +1,5 @@ -mod authentication; pub mod errors; +pub mod extractors; pub mod middlewares; pub mod server; pub mod viewmodels; diff --git a/httpw/src/middlewares/jwt.rs b/httpw/src/middlewares/jwt.rs deleted file mode 100644 index 6ab4765..0000000 --- a/httpw/src/middlewares/jwt.rs +++ /dev/null @@ -1,62 +0,0 @@ -use crate::viewmodels::HttpErrorViewModel; -use actix_web::error::ErrorUnauthorized; -use actix_web::{dev::Payload, Error as ActixWebError}; -use actix_web::{http, FromRequest, HttpMessage, HttpRequest}; -use env::AppConfig; -use jsonwebtoken::{decode, DecodingKey, Validation}; -use serde::{Deserialize, Serialize}; -use std::future::{ready, Ready}; - -#[derive(Debug, Serialize, Deserialize)] -pub struct TokenClaims { - pub sub: String, - pub iat: usize, - pub exp: usize, -} - -pub struct JwtAuthorizationMiddleware { - pub user_id: String, -} - -impl FromRequest for JwtAuthorizationMiddleware { - type Error = ActixWebError; - type Future = Ready>; - fn from_request(req: &HttpRequest, _: &mut Payload) -> Self::Future { - // let cfg = req.app_data::().unwrap(); - - let token = req - .headers() - .get(http::header::AUTHORIZATION) - .map(|h| h.to_str().unwrap().split_at(7).1.to_string()); - - if token.is_none() { - let json_error = HttpErrorViewModel { - status_code: http::StatusCode::UNAUTHORIZED.as_u16(), - message: "unauthorized".to_owned(), - details: "You are not logged in, please provide token".to_string(), - }; - return ready(Err(ErrorUnauthorized(json_error))); - } - - let claims = match decode::( - &token.unwrap(), - &DecodingKey::from_secret("".as_ref()), - &Validation::default(), - ) { - Ok(c) => c.claims, - Err(_) => { - let json_error = HttpErrorViewModel { - status_code: http::StatusCode::UNAUTHORIZED.as_u16(), - message: "unauthorized".to_owned(), - details: "invalid token".to_string(), - }; - return ready(Err(ErrorUnauthorized(json_error))); - } - }; - - let user_id = claims.sub; - req.extensions_mut().insert::(user_id.to_owned()); - - ready(Ok(JwtAuthorizationMiddleware { user_id })) - } -} diff --git a/httpw/src/middlewares/mod.rs b/httpw/src/middlewares/mod.rs index 9c2abfa..0e44b50 100644 --- a/httpw/src/middlewares/mod.rs +++ b/httpw/src/middlewares/mod.rs @@ -1,5 +1,4 @@ pub mod cors; pub mod deserializer; pub mod headers; -pub mod jwt; pub mod not_found; diff --git a/httpw/src/server/server.rs b/httpw/src/server/server.rs index 4276b27..fb2e09a 100644 --- a/httpw/src/server/server.rs +++ b/httpw/src/server/server.rs @@ -1,19 +1,23 @@ use super::types::RouteConfig; use crate::{errors::HttpServerError, middlewares}; use actix_web::{middleware as actix_middleware, web, App, HttpServer}; -use env::AppConfig as AppEnv; +use auth::jwt_manager::JwtManager; +use env::AppConfig; +use std::sync::Arc; use tracing::error; pub struct HttpwServerImpl { services: Vec, + jwt_manager: Option>, addr: String, } impl HttpwServerImpl { - pub fn new(cfg: &AppEnv) -> HttpwServerImpl { + pub fn new(cfg: &AppConfig) -> HttpwServerImpl { HttpwServerImpl { services: vec![], addr: cfg.app_addr(), + jwt_manager: None, } } } @@ -24,9 +28,15 @@ impl HttpwServerImpl { self } + pub fn jwt_manager(mut self, manager: Arc) -> Self { + self.jwt_manager = Some(manager); + self + } + pub async fn start(&self) -> Result<(), HttpServerError> { HttpServer::new({ let services = self.services.to_vec(); + let jwt_manager = self.jwt_manager.clone(); move || { let mut app = App::new() @@ -34,6 +44,12 @@ impl HttpwServerImpl { .wrap(middlewares::headers::config()) .wrap(middlewares::cors::config()); + if let Some(jwt_manager) = jwt_manager.clone() { + app = app.app_data(web::Data::>::new( + jwt_manager, + )); + } + for svc in services.clone() { app = app.configure(svc); } From 9ea404870727d6495f33ce957e1ba6e78075e96e Mon Sep 17 00:00:00 2001 From: ralvescosta Date: Sat, 11 Feb 2023 12:25:24 -0300 Subject: [PATCH 08/10] feat: auth0 --- auth/src/jwt_manager/auth0.rs | 38 +++++++++++++++++++++++------------ httpw/src/extractors/jwt.rs | 31 +++++++++++++++++++--------- 2 files changed, 47 insertions(+), 22 deletions(-) diff --git a/auth/src/jwt_manager/auth0.rs b/auth/src/jwt_manager/auth0.rs index e5108bd..8010acd 100644 --- a/auth/src/jwt_manager/auth0.rs +++ b/auth/src/jwt_manager/auth0.rs @@ -18,6 +18,7 @@ pub struct Auth0JwtManager { jwks: Mutex>, jwks_retrieved_at: SystemTime, authority: String, + app_domain: String, tracer: BoxedTracer, } @@ -26,7 +27,8 @@ impl Auth0JwtManager { Arc::new(Auth0JwtManager { jwks: Mutex::new(None), jwks_retrieved_at: SystemTime::now(), - authority: String::new(), //get from the config + authority: String::from("https://proteu.hedro.com.br"), //get from the config + app_domain: String::from("hdr-stg.us.auth0.com"), tracer: global::tracer("auth0_middleware"), }) } @@ -99,6 +101,12 @@ impl JwtManager for Auth0JwtManager { span.set_status(Status::Ok); + let t1 = claims.get("iss"); + let t2 = claims.get("sub"); + let t3 = claims.get("aud"); + let t4 = claims.get("iat"); + let t5 = claims.get("exp"); + Ok(TokenClaims::default()) } } @@ -140,20 +148,24 @@ impl Auth0JwtManager { } async fn get_jwks(&self, span: &mut BoxedSpan) -> Result { - 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"); + let res = match reqwest::get(&format!( + "https://{}/{}", + self.app_domain, ".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"), - }); + span.record_error(&err); + span.set_status(Status::Error { + description: Cow::from("error to get jwks from auth0 api"), + }); - Err(()) - } - Ok(r) => Ok(r), - }?; + Err(()) + } + Ok(r) => Ok(r), + }?; let val = match res.json::().await { Err(err) => { diff --git a/httpw/src/extractors/jwt.rs b/httpw/src/extractors/jwt.rs index 906dda0..cc1ba59 100644 --- a/httpw/src/extractors/jwt.rs +++ b/httpw/src/extractors/jwt.rs @@ -1,9 +1,8 @@ use crate::viewmodels::HttpErrorViewModel; -use actix_web::error::ErrorUnauthorized; +use actix_web::error::{ErrorUnauthorized, ErrorInternalServerError}; use actix_web::{dev::Payload, Error as ActixWebError}; use actix_web::{http, FromRequest, HttpRequest}; -use auth::jwt_manager::JwtManager; -use env::AppConfig; +use auth::jwt_manager::{JwtManager}; use opentelemetry::Context; use serde::{Deserialize, Serialize}; use std::future::{Future}; @@ -31,19 +30,33 @@ impl FromRequest for JwtAuthenticateExtractor { .get(http::header::AUTHORIZATION) .map(|h| h.to_str().unwrap().split_at(7).1.to_string()); - let jwt_manager = req.app_data::>().unwrap().clone(); - - Box::pin(async move { - let Some(token) = token else { + let Some(token) = token else { + return Box::pin(async move { let json_error = HttpErrorViewModel { status_code: http::StatusCode::UNAUTHORIZED.as_u16(), message: "unauthorized".to_owned(), details: "You are not logged in, please provide token".to_string(), }; - + return Err(ErrorUnauthorized(json_error)); - }; + }); + }; + + let Some(jwt_manager) = req.app_data::>() else { + return Box::pin(async move { + let json_error = HttpErrorViewModel { + status_code: http::StatusCode::INTERNAL_SERVER_ERROR.as_u16(), + message: "jwt manager internal error".to_owned(), + details: "no jwt manager was provided".to_string(), + }; + + return Err(ErrorInternalServerError(json_error)); + }); + }; + let jwt_manager = jwt_manager.clone(); + + Box::pin(async move { let Ok(claims) = jwt_manager.verify(&Context::new(), &token).await else { let json_error = HttpErrorViewModel { status_code: http::StatusCode::UNAUTHORIZED.as_u16(), From 2ba01cc1a80cafd865669c8b562bc34ffe245c7d Mon Sep 17 00:00:00 2001 From: ralvescosta Date: Sun, 12 Feb 2023 15:05:02 -0300 Subject: [PATCH 09/10] feat: auth0 --- Cargo.lock | 1 + auth/Cargo.toml | 2 + auth/src/jwt_manager/auth0.rs | 142 +++++++++++++++++++++++--------- auth/src/jwt_manager/manager.rs | 5 +- env/src/configs.rs | 2 + env/src/configs_builder.rs | 21 +++-- env/src/def.rs | 1 + httpw/src/extractors/jwt.rs | 22 ++--- httpw/src/extractors/mod.rs | 2 +- httpw/src/server/server.rs | 12 ++- 10 files changed, 138 insertions(+), 72 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 8c4d85d..46d4c0d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -489,6 +489,7 @@ version = "0.1.0" dependencies = [ "alcoholic_jwt", "async-trait", + "env", "opentelemetry", "reqwest", "serde", diff --git a/auth/Cargo.toml b/auth/Cargo.toml index 372e62d..bf0ff79 100644 --- a/auth/Cargo.toml +++ b/auth/Cargo.toml @@ -4,6 +4,8 @@ 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" } diff --git a/auth/src/jwt_manager/auth0.rs b/auth/src/jwt_manager/auth0.rs index 8010acd..5c66d0e 100644 --- a/auth/src/jwt_manager/auth0.rs +++ b/auth/src/jwt_manager/auth0.rs @@ -1,11 +1,13 @@ 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, @@ -18,17 +20,15 @@ pub struct Auth0JwtManager { jwks: Mutex>, jwks_retrieved_at: SystemTime, authority: String, - app_domain: String, tracer: BoxedTracer, } impl Auth0JwtManager { - pub fn new() -> Arc { + pub fn new(cfg: &AppConfig) -> Arc { Arc::new(Auth0JwtManager { jwks: Mutex::new(None), jwks_retrieved_at: SystemTime::now(), - authority: String::from("https://proteu.hedro.com.br"), //get from the config - app_domain: String::from("hdr-stg.us.auth0.com"), + authority: cfg.auth_authority.clone(), tracer: global::tracer("auth0_middleware"), }) } @@ -45,11 +45,9 @@ impl JwtManager for Auth0JwtManager { 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(()); } @@ -57,12 +55,10 @@ impl JwtManager for Auth0JwtManager { } 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(()) } }?; @@ -76,11 +72,9 @@ impl JwtManager for Auth0JwtManager { 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(()) } }?; @@ -89,25 +83,24 @@ impl JwtManager for Auth0JwtManager { 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); - let t1 = claims.get("iss"); - let t2 = claims.get("sub"); - let t3 = claims.get("aud"); - let t4 = claims.get("iat"); - let t5 = claims.get("exp"); - - Ok(TokenClaims::default()) + 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)?, + }) } } @@ -128,12 +121,10 @@ impl Auth0JwtManager { 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(()) } }?; @@ -148,39 +139,110 @@ impl Auth0JwtManager { } async fn get_jwks(&self, span: &mut BoxedSpan) -> Result { - let res = match reqwest::get(&format!( - "https://{}/{}", - self.app_domain, ".well-known/jwks.json" - )) - .await - { - Err(err) => { - error!(error = err.to_string(), "error to get jwks from auth0 api"); + 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::().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 to get jwks from auth0 api"), + description: Cow::from("error deserializing the jwks"), }); - Err(()) } - Ok(r) => Ok(r), + Ok(v) => Ok(v), }?; - let val = match res.json::().await { - Err(err) => { - error!(error = err.to_string(), "error deserializing the jwks"); + return Ok(val); + } - span.record_error(&err); + fn get_claim_as_string( + &self, + key: &str, + claims: &Value, + span: &mut BoxedSpan, + ) -> Result { + 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("error deserializing the jwks"), + description: Cow::from("invalid jwt claim"), }); + Err(()) + } + } + } + fn get_claim_as_u64(&self, key: &str, claims: &Value, span: &mut BoxedSpan) -> Result { + 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(()) } - Ok(v) => Ok(v), - }?; + } + } - return Ok(val); + fn get_claim_as_vec( + &self, + key: &str, + claims: &Value, + span: &mut BoxedSpan, + ) -> Result, ()> { + match claims.get(key) { + Some(fv) => match fv.as_array() { + Some(value) => Ok(value + .iter() + .map(|v| v.as_str().unwrap().to_owned()) + .collect::>()), + _ => { + 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(()) + } + } } } diff --git a/auth/src/jwt_manager/manager.rs b/auth/src/jwt_manager/manager.rs index 8f93f34..a1f4387 100644 --- a/auth/src/jwt_manager/manager.rs +++ b/auth/src/jwt_manager/manager.rs @@ -7,10 +7,9 @@ pub struct TokenClaims { pub iss: String, pub sub: String, pub aud: Vec, - pub iat: usize, - pub exp: usize, + pub iat: u64, + pub exp: u64, pub scope: String, - pub permissions: Vec, } #[async_trait] diff --git a/env/src/configs.rs b/env/src/configs.rs index 012dfe6..6f041fb 100644 --- a/env/src/configs.rs +++ b/env/src/configs.rs @@ -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 { diff --git a/env/src/configs_builder.rs b/env/src/configs_builder.rs index 4703db9..ea4906c 100644 --- a/env/src/configs_builder.rs +++ b/env/src/configs_builder.rs @@ -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, }; @@ -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, @@ -132,6 +134,7 @@ impl ConfigBuilder { name, port, secret_key, + auth_authority, }; self.app_cfg = app_cfg.clone(); diff --git a/env/src/def.rs b/env/src/def.rs index 4a23611..0cc5414 100644 --- a/env/src/def.rs +++ b/env/src/def.rs @@ -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"; diff --git a/httpw/src/extractors/jwt.rs b/httpw/src/extractors/jwt.rs index cc1ba59..6c9d257 100644 --- a/httpw/src/extractors/jwt.rs +++ b/httpw/src/extractors/jwt.rs @@ -1,23 +1,16 @@ use crate::viewmodels::HttpErrorViewModel; use actix_web::error::{ErrorUnauthorized, ErrorInternalServerError}; +use actix_web::web::Data; use actix_web::{dev::Payload, Error as ActixWebError}; use actix_web::{http, FromRequest, HttpRequest}; -use auth::jwt_manager::{JwtManager}; +use auth::jwt_manager::{JwtManager, TokenClaims}; use opentelemetry::Context; -use serde::{Deserialize, Serialize}; use std::future::{Future}; use std::pin::Pin; use std::sync::Arc; -#[derive(Debug, Serialize, Deserialize)] -pub struct TokenClaims { - pub sub: String, - pub iat: usize, - pub exp: usize, -} - pub struct JwtAuthenticateExtractor { - pub user_id: String, + pub claims: TokenClaims } impl FromRequest for JwtAuthenticateExtractor { @@ -42,7 +35,7 @@ impl FromRequest for JwtAuthenticateExtractor { }); }; - let Some(jwt_manager) = req.app_data::>() else { + let Some(jwt_manager) = req.app_data::>>() else { return Box::pin(async move { let json_error = HttpErrorViewModel { status_code: http::StatusCode::INTERNAL_SERVER_ERROR.as_u16(), @@ -61,16 +54,13 @@ impl FromRequest for JwtAuthenticateExtractor { let json_error = HttpErrorViewModel { status_code: http::StatusCode::UNAUTHORIZED.as_u16(), message: "unauthorized".to_owned(), - details: "You are not logged in, please provide token".to_string(), + details: "invalid token".to_string(), }; return Err(ErrorUnauthorized(json_error)); }; - // let user_id = claims.sub; - // req.extensions_mut().insert::(user_id.to_owned()); - - Ok(JwtAuthenticateExtractor { user_id: String::new() }) + Ok(JwtAuthenticateExtractor { claims }) }) } } diff --git a/httpw/src/extractors/mod.rs b/httpw/src/extractors/mod.rs index f700c00..48cdeb6 100644 --- a/httpw/src/extractors/mod.rs +++ b/httpw/src/extractors/mod.rs @@ -1,3 +1,3 @@ mod jwt; -pub use jwt::{JwtAuthenticateExtractor, TokenClaims}; +pub use jwt::JwtAuthenticateExtractor; diff --git a/httpw/src/server/server.rs b/httpw/src/server/server.rs index fb2e09a..c9e3565 100644 --- a/httpw/src/server/server.rs +++ b/httpw/src/server/server.rs @@ -1,6 +1,10 @@ use super::types::RouteConfig; use crate::{errors::HttpServerError, middlewares}; -use actix_web::{middleware as actix_middleware, web, App, HttpServer}; +use actix_web::{ + middleware as actix_middleware, + web::{self, Data}, + App, HttpServer, +}; use auth::jwt_manager::JwtManager; use env::AppConfig; use std::sync::Arc; @@ -45,8 +49,10 @@ impl HttpwServerImpl { .wrap(middlewares::cors::config()); if let Some(jwt_manager) = jwt_manager.clone() { - app = app.app_data(web::Data::>::new( - jwt_manager, + app = app.app_data::>>(web::Data::< + Arc, + >::new( + jwt_manager.clone(), )); } From c21ca3fb418635626160452f8fbf284a050da79d Mon Sep 17 00:00:00 2001 From: ralvescosta Date: Sun, 12 Feb 2023 15:22:59 -0300 Subject: [PATCH 10/10] fix: unit test --- Cargo.lock | 151 ++++++++++++++++++++++++----------------- mqtt/Cargo.toml | 9 +-- mqtt/src/dispatcher.rs | 31 +++++++-- 3 files changed, 119 insertions(+), 72 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 46d4c0d..24ebb84 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -342,9 +342,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.68" +version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2cb2f989d18dd141ab8ae82f64d1a8cdd37e0840f73a406896cf5e99502fab61" +checksum = "224afbd727c3d6e4b90103ece64b8d1b67fbb1973b1046c2281eed3f3803f800" [[package]] name = "async-channel" @@ -414,7 +414,7 @@ dependencies = [ "slab", "socket2", "waker-fn", - "windows-sys", + "windows-sys 0.42.0", ] [[package]] @@ -792,9 +792,9 @@ dependencies = [ [[package]] name = "axum" -version = "0.6.4" +version = "0.6.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5694b64066a2459918d8074c2ce0d5a88f409431994c2356617c8ae0c4721fc" +checksum = "4e246206a63c9830e118d12c894f56a82033da1a2361f5544deeee3df85c99d9" dependencies = [ "async-trait", "axum-core", @@ -1109,9 +1109,9 @@ dependencies = [ [[package]] name = "cxx" -version = "1.0.88" +version = "1.0.90" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "322296e2f2e5af4270b54df9e85a02ff037e271af20ba3e7fe1575515dc840b8" +checksum = "90d59d9acd2a682b4e40605a242f6670eaa58c5957471cbf85e8aa6a0b97a5e8" dependencies = [ "cc", "cxxbridge-flags", @@ -1121,9 +1121,9 @@ dependencies = [ [[package]] name = "cxx-build" -version = "1.0.88" +version = "1.0.90" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "017a1385b05d631e7875b1f151c9f012d37b53491e2a87f65bff5c262b2111d8" +checksum = "ebfa40bda659dd5c864e65f4c9a2b0aff19bea56b017b9b77c73d3766a453a38" dependencies = [ "cc", "codespan-reporting", @@ -1136,15 +1136,15 @@ dependencies = [ [[package]] name = "cxxbridge-flags" -version = "1.0.88" +version = "1.0.90" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c26bbb078acf09bc1ecda02d4223f03bdd28bd4874edcb0379138efc499ce971" +checksum = "457ce6757c5c70dc6ecdbda6925b958aae7f959bda7d8fb9bde889e34a09dc03" [[package]] name = "cxxbridge-macro" -version = "1.0.88" +version = "1.0.90" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "357f40d1f06a24b60ae1fe122542c1fb05d28d32acb2aed064e84bc2ad1e252e" +checksum = "ebf883b7aacd7b2aeb2a7b338648ee19f57c140d4ee8e52c68979c6b2f7f2263" dependencies = [ "proc-macro2", "quote", @@ -1283,9 +1283,9 @@ checksum = "7fcaabb2fef8c910e7f4c7ce9f67a1283a1715879a7c230ca9d6d1ae31f16d91" [[package]] name = "encoding_rs" -version = "0.8.31" +version = "0.8.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9852635589dc9f9ea1b6fe9f05b50ef208c85c834a562f0c6abb1c475736ec2b" +checksum = "071a31f4ee85403370b58aca746f01041ede6f0da2730960ad001edc2b71b394" dependencies = [ "cfg-if", ] @@ -1378,7 +1378,7 @@ dependencies = [ "futures-core", "futures-sink", "pin-project", - "spin 0.9.4", + "spin 0.9.5", ] [[package]] @@ -1612,9 +1612,9 @@ dependencies = [ [[package]] name = "heck" -version = "0.4.0" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2540771e65fc8cb83cd6e8a237f70c319bd5c29f78ed1084ba5d50eeac86f7f9" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" [[package]] name = "hermit-abi" @@ -1697,9 +1697,9 @@ dependencies = [ [[package]] name = "hyper" -version = "0.14.23" +version = "0.14.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "034711faac9d2166cb1baf1a2fb0b60b1f277f8492fd72176c17f3515e1abd3c" +checksum = "5e011372fa0b68db8350aa7a248930ecc7839bf46d8485577d69f117a75f164c" dependencies = [ "bytes", "futures-channel", @@ -1854,9 +1854,9 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.60" +version = "0.3.61" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49409df3e3bf0856b916e2ceaca09ee28e6871cf7d9ce97a692cacfdb2a25a47" +checksum = "445dde2150c55e483f3d8416706b97ec8e8237c307e5b7b4b8dd15e6af2a0730" dependencies = [ "wasm-bindgen", ] @@ -2046,7 +2046,7 @@ dependencies = [ "libc", "log", "wasi", - "windows-sys", + "windows-sys 0.42.0", ] [[package]] @@ -2372,15 +2372,15 @@ dependencies = [ [[package]] name = "parking_lot_core" -version = "0.9.6" +version = "0.9.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba1ef8814b5c993410bb3adfad7a5ed269563e4a2f90c41f5d85be7fb47133bf" +checksum = "9069cbb9f99e3a5083476ccb29ceb1de18b9118cafa53e90c9551235de2b9521" dependencies = [ "cfg-if", "libc", "redox_syscall", "smallvec", - "windows-sys", + "windows-sys 0.45.0", ] [[package]] @@ -2397,9 +2397,9 @@ checksum = "478c572c3d73181ff3c2539045f6eb99e5491218eae919370993b890cdbdd98e" [[package]] name = "petgraph" -version = "0.6.2" +version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6d5014253a1331579ce62aa67443b4a658c5e7dd03d4bc6d302b94474888143" +checksum = "4dd7d28ee937e54fe3080c91faa1c3a46c06de6252988a7f4592ba2310ef22a4" dependencies = [ "fixedbitset", "indexmap", @@ -2484,7 +2484,7 @@ dependencies = [ "libc", "log", "wepoll-ffi", - "windows-sys", + "windows-sys 0.42.0", ] [[package]] @@ -2566,9 +2566,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.50" +version = "1.0.51" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ef7d57beacfaf2d8aee5937dab7b7f28de3cb8b1828479bb5de2a7106f2bae2" +checksum = "5d727cae5b39d21da60fa540906919ad737832fe0b1c165da3a34d6548c849d6" dependencies = [ "unicode-ident", ] @@ -2866,7 +2866,7 @@ version = "0.1.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "713cfb06c7059f3588fb8044c0fad1d09e3c01d225e25b9220dbfdcf16dbb1b3" dependencies = [ - "windows-sys", + "windows-sys 0.42.0", ] [[package]] @@ -3010,9 +3010,9 @@ dependencies = [ [[package]] name = "signal-hook-registry" -version = "1.4.0" +version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e51e73328dc4ac0c7ccbda3a494dfa03df1de2f46018127f60c693f2648455b0" +checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1" dependencies = [ "libc", ] @@ -3056,9 +3056,9 @@ checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" [[package]] name = "spin" -version = "0.9.4" +version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f6002a767bff9e83f8eeecf883ecb8011875a21ae8da43bffb817a57e78cc09" +checksum = "7dccf47db1b41fa1573ed27ccf5e08e3ca771cb994f776668c5ebda893b248fc" dependencies = [ "lock_api", ] @@ -3104,9 +3104,9 @@ dependencies = [ [[package]] name = "sync_wrapper" -version = "0.1.1" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20518fe4a4c9acf048008599e464deb21beeae3d3578418951a189c235a7a9a8" +checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" [[package]] name = "tcp-stream" @@ -3171,10 +3171,11 @@ dependencies = [ [[package]] name = "thread_local" -version = "1.1.4" +version = "1.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5516c27b78311c50bf42c071425c560ac799b11c30b31f87e3081965fe5e0180" +checksum = "3fdd6f064ccff2d6567adcb3873ca630700f00b5ad3f060c25b5dcfd9a4ce152" dependencies = [ + "cfg-if", "once_cell", ] @@ -3216,9 +3217,9 @@ dependencies = [ [[package]] name = "tinyvec_macros" -version = "0.1.0" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" @@ -3236,7 +3237,7 @@ dependencies = [ "signal-hook-registry", "socket2", "tokio-macros", - "windows-sys", + "windows-sys 0.42.0", ] [[package]] @@ -3318,9 +3319,9 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.7.4" +version = "0.7.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bb2e075f03b3d66d8d8785356224ba688d2906a371015e225beeb65ca92c740" +checksum = "5427d89453009325de0d8f342c9490009f76e999cb7672d77e46267448f7e6b2" dependencies = [ "bytes", "futures-core", @@ -3648,9 +3649,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.83" +version = "0.2.84" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eaf9f5aceeec8be17c128b2e93e031fb8a4d469bb9c4ae2d7dc1888b26887268" +checksum = "31f8dcbc21f30d9b8f2ea926ecb58f6b91192c17e9d33594b3df58b2007ca53b" dependencies = [ "cfg-if", "wasm-bindgen-macro", @@ -3658,9 +3659,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.83" +version = "0.2.84" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c8ffb332579b0557b52d268b91feab8df3615f265d5270fec2a8c95b17c1142" +checksum = "95ce90fd5bcc06af55a641a86428ee4229e44e07033963a2290a8e241607ccb9" dependencies = [ "bumpalo", "log", @@ -3673,9 +3674,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.33" +version = "0.4.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23639446165ca5a5de86ae1d8896b737ae80319560fbaa4c2887b7da6e7ebd7d" +checksum = "f219e0d211ba40266969f6dbdd90636da12f75bee4fc9d6c23d1260dadb51454" dependencies = [ "cfg-if", "js-sys", @@ -3685,9 +3686,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.83" +version = "0.2.84" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "052be0f94026e6cbc75cdefc9bae13fd6052cdcaf532fa6c45e7ae33a1e6c810" +checksum = "4c21f77c0bedc37fd5dc21f897894a5ca01e7bb159884559461862ae90c0b4c5" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -3695,9 +3696,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.83" +version = "0.2.84" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07bc0c051dc5f23e307b13285f9d75df86bfdf816c5721e573dec1f9b8aa193c" +checksum = "2aff81306fcac3c7515ad4e177f521b5c9a15f2b08f4e32d823066102f35a5f6" dependencies = [ "proc-macro2", "quote", @@ -3708,15 +3709,15 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.83" +version = "0.2.84" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c38c045535d93ec4f0b4defec448e4291638ee608530863b1e2ba115d4fff7f" +checksum = "0046fef7e28c3804e5e38bfa31ea2a0f73905319b677e57ebe37e49358989b5d" [[package]] name = "web-sys" -version = "0.3.60" +version = "0.3.61" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bcda906d8be16e728fd5adc5b729afad4e444e106ab28cd1c7256e54fa61510f" +checksum = "e33b99f4b23ba3eec1a53ac264e35a755f00e966e0065077d6027c0f575b0b97" dependencies = [ "js-sys", "wasm-bindgen", @@ -3798,6 +3799,30 @@ dependencies = [ "windows_x86_64_msvc", ] +[[package]] +name = "windows-sys" +version = "0.45.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.42.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e2522491fbfcd58cc84d47aeb2958948c4b8982e9a2d8a2a35bbaed431390e7" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + [[package]] name = "windows_aarch64_gnullvm" version = "0.42.1" @@ -3878,9 +3903,9 @@ dependencies = [ [[package]] name = "zstd-safe" -version = "6.0.3+zstd.1.5.2" +version = "6.0.4+zstd.1.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68e4a3f57d13d0ab7e478665c60f35e2a613dcd527851c2c7287ce5c787e134a" +checksum = "7afb4b54b8910cf5447638cb54bf4e8a65cbedd783af98b98c62ffe91f185543" dependencies = [ "libc", "zstd-sys", @@ -3888,9 +3913,9 @@ dependencies = [ [[package]] name = "zstd-sys" -version = "2.0.6+zstd.1.5.2" +version = "2.0.7+zstd.1.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68a3f9792c0c3dc6c165840a75f47ae1f4da402c2d006881129579f6597e801b" +checksum = "94509c3ba2fe55294d752b79842c530ccfab760192521df74a081a78d2b3c7f5" dependencies = [ "cc", "libc", diff --git a/mqtt/Cargo.toml b/mqtt/Cargo.toml index a3965d7..36c251d 100644 --- a/mqtt/Cargo.toml +++ b/mqtt/Cargo.toml @@ -10,18 +10,19 @@ mocks = ["dep:mockall"] errors = { path = "../errors" } env = { path = "../env" } traces = { path = "../traces" } + opentelemetry = { version = "0.18.0" } tracing = { version = "0.1.37" } async-trait = { version = "0.1.64" } -bytes = { version = "1.2.1", features = ["serde"] } +bytes = { version = "1.4.0", features = ["serde"] } paho-mqtt = { version = "0.12.0" } serde = { version = "1.0.152", features = ["derive"] } -serde_json = { version = "1.0.91" } -futures-util = { version = "0.3.25" } +serde_json = { version = "1.0.93" } +futures-util = { version = "0.3.26" } # Used only with feature mock mockall = { version = "0.11.3", optional = true } [dev-dependencies] mockall = { version = "0.11.3" } -tokio = { version = "1.23.1" } \ No newline at end of file +tokio = { version = "1.25.0" } \ No newline at end of file diff --git a/mqtt/src/dispatcher.rs b/mqtt/src/dispatcher.rs index 1d99d9f..84946bc 100644 --- a/mqtt/src/dispatcher.rs +++ b/mqtt/src/dispatcher.rs @@ -138,6 +138,11 @@ impl MqttDispatcher { p = handler_topic_index as i16; } } + + if handler_fields.len() == received_fields.len() { + p = handler_topic_index as i16; + break; + } } if p == -1 { @@ -172,7 +177,7 @@ mod tests { fn test_declare() { let mut dispatch = MqttDispatcher::new(); - let res = dispatch.declare("/some/topic", Arc::new(MockDispatch::new())); + let res = dispatch.declare("some/topic", Arc::new(MockDispatch::new())); assert!(res.is_ok()); let res = dispatch.declare("", Arc::new(MockDispatch::new())); @@ -183,10 +188,23 @@ mod tests { async fn test_consume() { let mut dispatch = MqttDispatcher::new(); - let res = dispatch.declare("/some/topic/#", Arc::new(MockDispatch::new())); + let res = dispatch.declare("some/topic/#", Arc::new(MockDispatch::new())); assert!(res.is_ok()); - let msg = Message::new("/some/topic/sub", vec![], 0); + let msg = Message::new("some/topic/sub/1", vec![], 0); + + let res = dispatch.consume(&Context::new(), &msg).await; + assert!(res.is_ok()); + } + + #[tokio::test] + async fn test_consume_with_plus_wildcard() { + let mut dispatch = MqttDispatcher::new(); + + let res = dispatch.declare("some/+/+/sub", Arc::new(MockDispatch::new())); + assert!(res.is_ok()); + + let msg = Message::new("some/topic/with/sub", vec![], 0); let res = dispatch.consume(&Context::new(), &msg).await; assert!(res.is_ok()); @@ -210,9 +228,12 @@ mod tests { #[tokio::test] async fn test_consume_with_unregistered_consumer() { - let dispatch = MqttDispatcher::new(); + let mut dispatch = MqttDispatcher::new(); - let msg = Message::new("/some/topic/sub", vec![], 0); + let res = dispatch.declare("other/topic/#", Arc::new(MockDispatch::new())); + assert!(res.is_ok()); + + let msg = Message::new("some/topic/sub", vec![], 0); let res = dispatch.consume(&Context::new(), &msg).await; assert!(res.is_err());