Create a new JWKS cache, discovering the JWKS URI and fetching the initial key set.
(config: &OidcConfig)
| 168 | /// Create a new JWKS cache, discovering the JWKS URI and fetching the |
| 169 | /// initial key set. |
| 170 | pub async fn new(config: &OidcConfig) -> Result<Self, String> { |
| 171 | let http = Client::builder() |
| 172 | .timeout(Duration::from_secs(10)) |
| 173 | .build() |
| 174 | .map_err(|e| format!("failed to create HTTP client: {e}"))?; |
| 175 | |
| 176 | // Discover JWKS URI from the OIDC discovery endpoint. |
| 177 | let discovery_url = format!( |
| 178 | "{}/.well-known/openid-configuration", |
| 179 | config.issuer.trim_end_matches('/') |
| 180 | ); |
| 181 | info!(url = %discovery_url, "Discovering OIDC configuration"); |
| 182 | |
| 183 | let discovery: OidcDiscovery = http |
| 184 | .get(&discovery_url) |
| 185 | .send() |
| 186 | .await |
| 187 | .map_err(|e| format!("OIDC discovery request failed: {e}"))? |
| 188 | .json() |
| 189 | .await |
| 190 | .map_err(|e| format!("OIDC discovery response parse failed: {e}"))?; |
| 191 | |
| 192 | // Validate the discovery document's issuer matches our configured issuer. |
| 193 | let expected = config.issuer.trim_end_matches('/'); |
| 194 | let actual = discovery.issuer.trim_end_matches('/'); |
| 195 | if expected != actual { |
| 196 | return Err(format!( |
| 197 | "OIDC discovery issuer mismatch: expected '{expected}', got '{actual}'" |
| 198 | )); |
| 199 | } |
| 200 | |
| 201 | info!(jwks_uri = %discovery.jwks_uri, "OIDC JWKS URI discovered"); |
| 202 | |
| 203 | let cache = Self { |
| 204 | keys: Arc::new(RwLock::new(HashMap::new())), |
| 205 | jwks_uri: discovery.jwks_uri, |
| 206 | ttl: Duration::from_secs(config.jwks_ttl_secs), |
| 207 | last_refresh: Arc::new(RwLock::new( |
| 208 | Instant::now() |
| 209 | .checked_sub(Duration::from_secs(config.jwks_ttl_secs + 1)) |
| 210 | .unwrap_or_else(Instant::now), |
| 211 | )), |
| 212 | refresh_mutex: tokio::sync::Mutex::new(()), |
| 213 | http, |
| 214 | config: config.clone(), |
| 215 | }; |
| 216 | |
| 217 | cache.refresh_keys().await?; |
| 218 | Ok(cache) |
| 219 | } |
| 220 | |
| 221 | /// Fetch the JWKS and update the cached keys. |
| 222 | async fn refresh_keys(&self) -> Result<(), String> { |
nothing calls this directly
no test coverage detected