diff --git a/crates/oidc-provider/src/cache.rs b/crates/oidc-provider/src/cache.rs index 9ef156a..e684f52 100644 --- a/crates/oidc-provider/src/cache.rs +++ b/crates/oidc-provider/src/cache.rs @@ -16,21 +16,22 @@ use crate::BackendCredentials; const EXPIRY_MARGIN_SECS: i64 = 60; /// Thread-safe TTL cache for cloud credentials. +/// +/// `Clone` shares the same underlying store (the entries map is behind an +/// `Arc`), so a cloned [`OidcCredentialProvider`](crate::OidcCredentialProvider) +/// keeps hitting the same cache — letting a runtime hold the provider in a +/// shared/`static` slot and reuse it across requests instead of re-minting and +/// re-exchanging every time. +#[derive(Clone, Default)] pub struct CredentialCache { - entries: Mutex>>, -} - -impl Default for CredentialCache { - fn default() -> Self { - Self::new() - } + entries: Arc>>>, } impl CredentialCache { /// Create an empty credential cache. pub fn new() -> Self { Self { - entries: Mutex::new(HashMap::new()), + entries: Arc::new(Mutex::new(HashMap::new())), } } diff --git a/crates/oidc-provider/src/exchange/aws.rs b/crates/oidc-provider/src/exchange/aws.rs index 821a4b7..f61e19b 100644 --- a/crates/oidc-provider/src/exchange/aws.rs +++ b/crates/oidc-provider/src/exchange/aws.rs @@ -83,11 +83,7 @@ impl AwsExchange { } impl CredentialExchange for AwsExchange { - async fn exchange( - &self, - http: &H, - jwt: &str, - ) -> Result { + async fn exchange(&self, http: &H, jwt: &str) -> Result { // Build the request with this module's `AssumeRoleWithWebIdentity`, hand // its (unencoded) pairs to the runtime's HTTP client — which // form-urlencodes them — then parse the reply. diff --git a/crates/oidc-provider/src/exchange/azure.rs b/crates/oidc-provider/src/exchange/azure.rs index af55c99..31403f7 100644 --- a/crates/oidc-provider/src/exchange/azure.rs +++ b/crates/oidc-provider/src/exchange/azure.rs @@ -49,11 +49,7 @@ impl AzureExchange { } impl CredentialExchange for AzureExchange { - async fn exchange( - &self, - http: &H, - jwt: &str, - ) -> Result { + async fn exchange(&self, http: &H, jwt: &str) -> Result { let form = [ ("grant_type", "client_credentials"), ( diff --git a/crates/oidc-provider/src/exchange/gcp.rs b/crates/oidc-provider/src/exchange/gcp.rs index cd4959a..c332735 100644 --- a/crates/oidc-provider/src/exchange/gcp.rs +++ b/crates/oidc-provider/src/exchange/gcp.rs @@ -51,11 +51,7 @@ impl GcpExchange { } impl CredentialExchange for GcpExchange { - async fn exchange( - &self, - http: &H, - jwt: &str, - ) -> Result { + async fn exchange(&self, http: &H, jwt: &str) -> Result { // Step 1: Exchange JWT for federated access token via GCP STS let sts_form = [ ( diff --git a/crates/oidc-provider/src/lib.rs b/crates/oidc-provider/src/lib.rs index 18d642d..a383a00 100644 --- a/crates/oidc-provider/src/lib.rs +++ b/crates/oidc-provider/src/lib.rs @@ -56,6 +56,11 @@ pub trait HttpExchange: } /// Top-level provider that combines signing, exchange, and caching. +/// +/// `Clone` is cheap and shares the credential cache (and the `Clone` HTTP +/// client), so a runtime can construct one provider and reuse it across requests +/// — keeping the cache warm instead of re-minting + re-exchanging every call. +#[derive(Clone)] pub struct OidcCredentialProvider { signer: JwtSigner, cache: CredentialCache,