use crate::actors::providers_states_actor::ProviderLoginState; use std::cell::RefCell; use std::collections::HashMap; use crate::constants::{OIDC_PROVIDERS_LIFETIME, OIDC_PROVIDER_CB_URI}; use crate::data::app_config::AppConfig; use crate::data::jwt_signer::JsonWebKey; use crate::data::provider::Provider; use crate::utils::err::Res; use crate::utils::time::time; #[derive(Debug, Clone, serde::Deserialize)] pub struct ProviderDiscovery { pub issuer: String, pub authorization_endpoint: String, pub token_endpoint: String, pub userinfo_endpoint: Option, pub jwks_uri: String, pub claims_supported: Option>, } #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] pub struct ProviderJWKs { pub keys: Vec, } /// Provider configuration #[derive(Debug, Clone)] pub struct ProviderConfiguration { pub discovery: ProviderDiscovery, pub keys: ProviderJWKs, pub expire: u64, } impl ProviderConfiguration { /// Get the URL where a user should be redirected to authenticate pub fn auth_url(&self, provider: &Provider, state: &ProviderLoginState) -> String { let authorization_url = &self.discovery.authorization_endpoint; let client_id = urlencoding::encode(&provider.client_id).to_string(); let state = urlencoding::encode(&state.state_id).to_string(); let callback_url = AppConfig::get().full_url(OIDC_PROVIDER_CB_URI); format!("{authorization_url}?response_type=code&scope=openid%20profile%20email&client_id={client_id}&state={state}&redirect_uri={callback_url}") } } thread_local! { static THREAD_CACHE: RefCell> = RefCell::new(Default::default()); } pub struct ProviderConfigurationHelper {} impl ProviderConfigurationHelper { /// Get or refresh the configuration for a provider pub async fn get_configuration(provider: &Provider) -> Res { let config = THREAD_CACHE.with(|i| i.borrow().get(&provider.configuration_url).cloned()); // Refresh config cache if needed if config.is_none() || config.as_ref().unwrap().expire < time() { let conf = Self::fetch_configuration(provider).await?; THREAD_CACHE.with(|i| { i.borrow_mut() .insert(provider.configuration_url.clone(), conf.clone()) }); return Ok(conf); } // We can return immediately previously extracted value Ok(config.unwrap()) } /// Get fresh configuration from provider async fn fetch_configuration(provider: &Provider) -> Res { let discovery: ProviderDiscovery = reqwest::get(&provider.configuration_url) .await? .json() .await?; let keys: ProviderJWKs = reqwest::get(&discovery.jwks_uri).await?.json().await?; Ok(ProviderConfiguration { discovery, keys, expire: time() + OIDC_PROVIDERS_LIFETIME, }) } }