diff --git a/.env.template b/.env.template index 4f617c5f..dc12bc3b 100644 --- a/.env.template +++ b/.env.template @@ -445,6 +445,8 @@ # SSO_MASTER_PASSWORD_POLICY='{"enforceOnLogin":false,"minComplexity":3,"minLength":12,"requireLower":false,"requireNumbers":false,"requireSpecial":false,"requireUpper":false}' ## Use sso only for authentication not the session lifecycle # SSO_AUTH_ONLY_NOT_SESSION=false +## Client cache for discovery endpoint. Duration in seconds (0 to disable). +# SSO_CLIENT_CACHE_EXPIRATION=0 ## Log all the tokens, LOG_LEVEL=debug is required # SSO_DEBUG_TOKENS=false diff --git a/SSO.md b/SSO.md index 8d622ad4..67f73abf 100644 --- a/SSO.md +++ b/SSO.md @@ -26,6 +26,7 @@ The following configurations are available - `SSO_CLIENT_SECRET` : Client Secret - `SSO_MASTER_PASSWORD_POLICY`: Optional Master password policy - `SSO_AUTH_ONLY_NOT_SESSION`: Enable to use SSO only for authentication not session lifecycle + - `SSO_CLIENT_CACHE_EXPIRATION`: Cache calls to the discovery endpoint, duration in seconds, `0` to disable (default `0`); - `SSO_DEBUG_TOKENS`: Log all tokens (default `false`, `LOG_LEVEL=debug` is required) The callback url is : `https://your.domain/identity/connect/oidc-signin` diff --git a/src/config.rs b/src/config.rs index ce5d64d1..3bb7fd70 100644 --- a/src/config.rs +++ b/src/config.rs @@ -636,6 +636,8 @@ make_config! { sso_master_password_policy: String, true, option; /// Use sso only for auth not the session lifecycle sso_auth_only_not_session: bool, true, def, false; + /// Client cache for discovery endpoint. Duration in seconds (0 or less to disable). + sso_client_cache_expiration: u64, true, def, 0; /// Log all tokens, LOG_LEVEL=debug is required sso_debug_tokens: bool, true, def, false; }, diff --git a/src/sso.rs b/src/sso.rs index 88fb58d5..cf1b0217 100644 --- a/src/sso.rs +++ b/src/sso.rs @@ -1,7 +1,6 @@ use chrono::Utc; use regex::Regex; use std::borrow::Cow; -use std::sync::RwLock; use std::time::Duration; use url::Url; @@ -31,7 +30,10 @@ use crate::{ static AC_CACHE: Lazy> = Lazy::new(|| Cache::builder().max_capacity(1000).time_to_live(Duration::from_secs(10 * 60)).build()); -static CLIENT_CACHE: RwLock> = RwLock::new(None); +static CLIENT_CACHE_KEY: Lazy = Lazy::new(|| "sso-client".to_string()); +static CLIENT_CACHE: Lazy> = Lazy::new(|| { + Cache::builder().max_capacity(1).time_to_live(Duration::from_secs(CONFIG.sso_client_cache_expiration())).build() +}); static SSO_JWT_ISSUER: Lazy = Lazy::new(|| format!("{}|sso", CONFIG.domain_origin())); @@ -165,14 +167,17 @@ impl CoreClientExt for CoreClient { // Simple cache to prevent recalling the discovery endpoint each time async fn cached() -> ApiResult { - let cc_client = CLIENT_CACHE.read().ok().and_then(|rw_lock| rw_lock.clone()); - match cc_client { - Some(client) => Ok(client), - None => Self::_get_client().await.map(|client| { - let mut cached_client = CLIENT_CACHE.write().unwrap(); - *cached_client = Some(client.clone()); - client - }), + if CONFIG.sso_client_cache_expiration() > 0 { + match CLIENT_CACHE.get(&*CLIENT_CACHE_KEY) { + Some(client) => Ok(client), + None => Self::_get_client().await.map(|client| { + debug!("Inserting new client in cache"); + CLIENT_CACHE.insert(CLIENT_CACHE_KEY.clone(), client.clone()); + client + }), + } + } else { + Self::_get_client().await } } @@ -347,8 +352,13 @@ pub async fn exchange_code(wrapped_code: &str, conn: &mut DbConn) -> ApiResult err!(format!("Could not read id_token claims, {err}")), Ok(claims) => claims, + Err(err) => { + if CONFIG.sso_client_cache_expiration() > 0 { + CLIENT_CACHE.invalidate(&*CLIENT_CACHE_KEY); + } + err!(format!("Could not read id_token claims, {err}")); + } }; let email = match id_claims.email() {