From df31c8b3dc551e2555d7c3f49c9f44a03bfb870b Mon Sep 17 00:00:00 2001 From: MaeIsBad <26093674+MaeIsBad@users.noreply.github.com> Date: Fri, 23 Aug 2024 14:20:11 +0200 Subject: [PATCH] [PLATFORM-2239]: Fix backward incompatibility with previous bridge.rs versions (#167) --- CHANGELOG.md | 30 +++++++++++++++++++++- Cargo.toml | 5 ++-- src/auth0/cache/mod.rs | 4 ++- src/auth0/errors.rs | 2 -- src/auth0/mod.rs | 57 ++++++++++++++++-------------------------- 5 files changed, 55 insertions(+), 43 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4342479..2489144 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,32 @@ and this project adheres to --- +## [0.16.6] - 2024-08-23 + +### Removed + +- The library no longer validates tokens after recieving them from auth0 + +This was unneccessary, already wasn't done in some code paths, and as a bonus let us remove a dependency. + +### Changed + +- When first creating the client if bridge.rs fails to decrypt a cached token a warning will be logged, and a new token will be fetched + +This behavior matches what happens when a token is automatically refreshed during the applications runtime, and should help address issues that might come up in the future. + +- The cache key now contains a cache version, allowing it's schema to be updated in the future + +From now on cache keys will use the following format: + +`auth0rs_tokens:{caller}:{token_version}:{audience}"` + +eg. + +`auth0rs_tokens:wingman:2:galactus"` + +--- + ## [0.16.5] - 2024-07-10 ### Security @@ -425,7 +451,9 @@ Request::rest(&bridge).send() The old API is still available but deprecated. It will be removed soon. -[Unreleased]: https://github.com/primait/bridge.rs/compare/0.16.5...HEAD + +[Unreleased]: https://github.com/primait/bridge.rs/compare/0.16.6...HEAD +[0.16.6]: https://github.com/primait/bridge.rs/compare/0.16.5...0.16.6 [0.16.5]: https://github.com/primait/bridge.rs/compare/0.16.4...0.16.5 [0.16.3]: https://github.com/primait/bridge.rs/compare/0.16.2...0.16.2 [0.16.2]: https://github.com/primait/bridge.rs/compare/0.16.1...0.16.2 diff --git a/Cargo.toml b/Cargo.toml index 2f23a37..8d4c431 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,14 +6,14 @@ license = "MIT" name = "prima_bridge" readme = "README.md" repository = "https://github.com/primait/bridge.rs" -version = "0.16.5" +version = "0.16.6" # See https://github.com/rust-lang/rust/issues/107557 rust-version = "1.72" [features] default = ["tracing_opentelemetry"] -auth0 = ["rand", "redis", "jsonwebtoken", "jwks_client_rs", "chrono", "chacha20poly1305", "dashmap", "tracing"] +auth0 = ["rand", "redis", "jsonwebtoken", "chrono", "chacha20poly1305", "dashmap", "tracing"] gzip = ["reqwest/gzip"] redis-tls = ["redis/tls", "redis/tokio-native-tls-comp"] tracing_opentelemetry = [ "tracing_opentelemetry_0_23" ] @@ -33,7 +33,6 @@ dashmap = {version = "6.0", optional = true} futures = "0.3" futures-util = "0.3" jsonwebtoken = {version = "9.0", optional = true} -jwks_client_rs = {version = "0.5", optional = true} rand = {version = "0.8", optional = true} redis = {version = "0.23", features = ["tokio-comp"], optional = true} reqwest = {version = "0.12", features = ["json", "multipart", "stream"]} diff --git a/src/auth0/cache/mod.rs b/src/auth0/cache/mod.rs index 01d671d..6160a51 100644 --- a/src/auth0/cache/mod.rs +++ b/src/auth0/cache/mod.rs @@ -9,6 +9,8 @@ mod inmemory; mod redis_impl; const TOKEN_PREFIX: &str = "auth0rs_tokens"; +// The version of the token for backwards incompatible changes +const TOKEN_VERSION: &str = "2"; #[async_trait::async_trait] pub trait Cache: Send + Sync + std::fmt::Debug { @@ -18,5 +20,5 @@ pub trait Cache: Send + Sync + std::fmt::Debug { } pub(in crate::auth0::cache) fn token_key(caller: &str, audience: &str) -> String { - format!("{}:{}:{}", TOKEN_PREFIX, caller, audience) + format!("{}:{}:{}:{}", TOKEN_PREFIX, caller, TOKEN_VERSION, audience) } diff --git a/src/auth0/errors.rs b/src/auth0/errors.rs index 7512734..54b3333 100644 --- a/src/auth0/errors.rs +++ b/src/auth0/errors.rs @@ -10,8 +10,6 @@ pub enum Auth0Error { JwtFetchError(u16, String, reqwest::Error), #[error("failed to deserialize jwt from {0}. {1}")] JwtFetchDeserializationError(String, reqwest::Error), - #[error(transparent)] - JwksClientError(#[from] jwks_client_rs::JwksClientError), #[error("failed to fetch jwt from {0}. Status code: {0}; error: {1}")] JwksHttpError(String, reqwest::Error), #[error("redis error: {0}")] diff --git a/src/auth0/mod.rs b/src/auth0/mod.rs index cd1b737..c8c8984 100644 --- a/src/auth0/mod.rs +++ b/src/auth0/mod.rs @@ -1,10 +1,7 @@ //! Stuff used to provide JWT authentication via Auth0 use std::sync::{Arc, RwLock, RwLockReadGuard, RwLockWriteGuard}; -use std::time::Duration; -use jwks_client_rs::source::WebSource; -use jwks_client_rs::JwksClient; use reqwest::Client; use tokio::task::JoinHandle; use tokio::time::Interval; @@ -14,7 +11,6 @@ pub use errors::Auth0Error; use util::ResultExt; use crate::auth0::cache::Cache; -use crate::auth0::token::Claims; pub use crate::auth0::token::Token; mod cache; @@ -36,25 +32,10 @@ impl Auth0 { Arc::new(cache::RedisCache::new(&config).await?) }; - let source: WebSource = WebSource::builder() - .with_timeout(Duration::from_secs(5)) - .with_connect_timeout(Duration::from_secs(55)) - .build(config.jwks_url().to_owned()) - .map_err(|err| Auth0Error::JwksHttpError(config.token_url().as_str().to_string(), err))?; - - let jwks_client = JwksClient::builder().build(source); let token: Token = get_token(client_ref, &cache, &config).await?; - let token_lock: Arc> = Arc::new(RwLock::new(token)); - start( - token_lock.clone(), - jwks_client.clone(), - client_ref.clone(), - cache.clone(), - config, - ) - .await; + start(token_lock.clone(), client_ref.clone(), cache.clone(), config).await; Ok(Self { token_lock }) } @@ -66,7 +47,6 @@ impl Auth0 { async fn start( token_lock: Arc>, - jwks_client: JwksClient, client: Client, cache: Arc, config: Config, @@ -89,15 +69,8 @@ async fn start( if token.needs_refresh(&config) { tracing::info!("Refreshing JWT and JWKS"); - match Token::fetch(&client, &config).await { + match fetch_and_update_token(&client, &cache, &config).await { Ok(token) => { - let is_signed: bool = jwks_client - .decode::(token.as_str(), &[config.audience()]) - .await - .is_ok(); - tracing::info!("is signed: {}", is_signed); - - let _ = cache.put_token(&token).await.log_err("Error caching JWT"); write(&token_lock, token); } Err(error) => tracing::error!("Failed to fetch JWT. Reason: {:?}", error), @@ -111,17 +84,29 @@ async fn start( // Try to fetch the token from cache. If it's found return it; fetch from auth0 and put in cache otherwise async fn get_token(client_ref: &Client, cache_ref: &Arc, config_ref: &Config) -> Result { - match cache_ref.get_token().await? { - Some(token) => Ok(token), - None => { - let token: Token = Token::fetch(client_ref, config_ref).await?; - let _ = cache_ref.put_token(&token).await.log_err("JWT cache set failed"); - - Ok(token) + match cache_ref.get_token().await { + Ok(Some(token)) => Ok(token), + Ok(None) => fetch_and_update_token(client_ref, cache_ref, config_ref).await, + Err(Auth0Error::CryptoError(e)) => { + tracing::warn!("Crypto error({}) when attempting to decrypt cached token. Ignoring", e); + fetch_and_update_token(client_ref, cache_ref, config_ref).await } + Err(e) => Err(e), } } +// Unconditionally fetch a new token and update the cache +async fn fetch_and_update_token( + client_ref: &Client, + cache_ref: &Arc, + config_ref: &Config, +) -> Result { + let token: Token = Token::fetch(client_ref, config_ref).await?; + let _ = cache_ref.put_token(&token).await.log_err("JWT cache set failed"); + + Ok(token) +} + fn read(lock_ref: &Arc>) -> T { let lock_guard: RwLockReadGuard = lock_ref.read().unwrap_or_else(|poison_error| poison_error.into_inner()); (*lock_guard).clone()