ones-oidc 0.3.7

ONES OpenID Connect client for Rust
Documentation
use crate::config::OnesOidcConfig;
use crate::errors::DiscoveryError;
use log::{debug, info, warn};
use openidconnect::core::CoreProviderMetadata;
use openidconnect::reqwest::async_http_client;
use openidconnect::IssuerUrl;
use std::time::Duration;
use tokio::time::sleep;

/// Discover OIDC provider metadata with retry and exponential backoff.
///
/// This function attempts to discover the OIDC provider metadata from the given issuer URL.
/// If the discovery fails (e.g., due to network unavailability), it will retry with
/// exponential backoff according to the configuration.
///
/// # Arguments
///
/// * `issuer_url` - The OIDC issuer URL to discover metadata from
/// * `config` - Configuration containing retry settings
///
/// # Returns
///
/// Returns the discovered `CoreProviderMetadata` on success, or a `DiscoveryError` if
/// all retry attempts fail.
///
/// # Example
///
/// ```rust,no_run
/// use ones_oidc::{discover_provider_metadata, OnesOidcConfig};
/// use openidconnect::IssuerUrl;
///
/// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
/// let issuer_url = IssuerUrl::new("https://idp.example.com".to_string())?;
/// let config = OnesOidcConfig::default();
///
/// let provider_metadata = discover_provider_metadata(&issuer_url, &config).await?;
/// # Ok(())
/// # }
/// ```
pub async fn discover_provider_metadata(
    issuer_url: &IssuerUrl,
    config: &OnesOidcConfig,
) -> Result<CoreProviderMetadata, DiscoveryError> {
    let max_attempts = config.max_discovery_retries + 1; // +1 for initial attempt
    let mut current_backoff = config.discovery_backoff;
    let mut last_error = String::new();

    for attempt in 1..=max_attempts {
        debug!(
            "OIDC discovery attempt {}/{} for {}",
            attempt,
            max_attempts,
            issuer_url.as_str()
        );

        match CoreProviderMetadata::discover_async(issuer_url.clone(), async_http_client).await {
            Ok(metadata) => {
                if attempt > 1 {
                    info!(
                        "OIDC discovery succeeded on attempt {} for {}",
                        attempt,
                        issuer_url.as_str()
                    );
                }
                return Ok(metadata);
            }
            Err(e) => {
                last_error = e.to_string();

                if attempt < max_attempts {
                    warn!(
                        "OIDC discovery attempt {}/{} failed: {}. Retrying in {:?}...",
                        attempt, max_attempts, last_error, current_backoff
                    );
                    sleep(current_backoff).await;
                    current_backoff = Duration::from_secs(
                        (current_backoff.as_secs() * 2).min(60), // Cap at 60 seconds
                    );
                }
            }
        }
    }

    Err(DiscoveryError::DiscoveryFailed {
        attempts: max_attempts,
        message: last_error,
    })
}

/// Discover OIDC provider metadata from an issuer URL string with retry and exponential backoff.
///
/// Convenience wrapper around `discover_provider_metadata` that accepts a string URL.
///
/// # Arguments
///
/// * `issuer_url_str` - The OIDC issuer URL string
/// * `config` - Configuration containing retry settings
///
/// # Returns
///
/// Returns the discovered `CoreProviderMetadata` on success, or a `DiscoveryError` if
/// the URL is invalid or all retry attempts fail.
pub async fn discover_provider_metadata_from_str(
    issuer_url_str: &str,
    config: &OnesOidcConfig,
) -> Result<CoreProviderMetadata, DiscoveryError> {
    let issuer_url = IssuerUrl::new(issuer_url_str.to_string())
        .map_err(|e| DiscoveryError::InvalidIssuerUrl(e.to_string()))?;

    discover_provider_metadata(&issuer_url, config).await
}