use std::cell::RefCell; use std::collections::HashMap; use light_openid::primitives::{OpenIDConfig, OpenIDTokenResponse, OpenIDUserInfo}; use crate::actors::providers_states_actor::ProviderLoginState; use crate::constants::OIDC_PROVIDERS_LIFETIME; use crate::data::app_config::AppConfig; use crate::data::provider::Provider; use crate::utils::err::Res; use crate::utils::time::time; /// Provider configuration #[derive(Debug, Clone)] pub struct ProviderConfiguration { pub discovery: OpenIDConfig, 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().oidc_provider_redirect_url(); format!("{authorization_url}?response_type=code&scope=openid%20profile%20email&client_id={client_id}&state={state}&redirect_uri={callback_url}") } /// Retrieve the authorization token after a successful authentication, using an authorization code pub async fn get_token( &self, provider: &Provider, authorization_code: &str, ) -> Res { let (token, _) = self .discovery .request_token( &provider.client_id, &provider.client_secret, authorization_code, &AppConfig::get().oidc_provider_redirect_url(), ) .await?; Ok(token) } /// Retrieve information about the user, using a given [OpenIDTokenResponse] pub async fn get_userinfo(&self, token: &OpenIDTokenResponse) -> Res { Ok(self.discovery.request_user_info(token).await?.0) } } 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 { Ok(ProviderConfiguration { discovery: OpenIDConfig::load_from_url(&provider.configuration_url).await?, expire: time() + OIDC_PROVIDERS_LIFETIME, }) } }