Skip to content

Commit

Permalink
[PLATFORM-2239]: Fix backward incompatibility with previous bridge.rs…
Browse files Browse the repository at this point in the history
… versions (#167)
  • Loading branch information
MaeIsBad authored Aug 23, 2024
1 parent 769f953 commit df31c8b
Show file tree
Hide file tree
Showing 5 changed files with 55 additions and 43 deletions.
30 changes: 29 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
5 changes: 2 additions & 3 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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" ]
Expand All @@ -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"]}
Expand Down
4 changes: 3 additions & 1 deletion src/auth0/cache/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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)
}
2 changes: 0 additions & 2 deletions src/auth0/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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}")]
Expand Down
57 changes: 21 additions & 36 deletions src/auth0/mod.rs
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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;
Expand All @@ -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<RwLock<Token>> = 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 })
}
Expand All @@ -66,7 +47,6 @@ impl Auth0 {

async fn start(
token_lock: Arc<RwLock<Token>>,
jwks_client: JwksClient<WebSource>,
client: Client,
cache: Arc<dyn Cache>,
config: Config,
Expand All @@ -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::<Claims>(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),
Expand All @@ -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<dyn Cache>, config_ref: &Config) -> Result<Token, Auth0Error> {
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<dyn Cache>,
config_ref: &Config,
) -> Result<Token, Auth0Error> {
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<T: Clone>(lock_ref: &Arc<RwLock<T>>) -> T {
let lock_guard: RwLockReadGuard<T> = lock_ref.read().unwrap_or_else(|poison_error| poison_error.into_inner());
(*lock_guard).clone()
Expand Down

0 comments on commit df31c8b

Please sign in to comment.