ones-oidc 0.3.4

ONES OpenID Connect client for Rust
Documentation
use std::time::Duration;

/// Default JWT expiration time in seconds
const DEFAULT_JWT_EXPIRATION_SECS: u64 = 300;

/// Default signing service URL (localhost for development)
const DEFAULT_SIGNING_SERVICE_URL: &str = "http://localhost:8000/signature";

/// Default HTTP timeout in seconds
const DEFAULT_HTTP_TIMEOUT_SECS: u64 = 3;

/// Configuration for the ONES OIDC client
#[derive(Debug, Clone)]
pub struct OnesOidcConfig {
    /// HTTP timeout duration
    pub http_timeout: Duration,
    /// User agent string for HTTP requests
    pub user_agent: Option<String>,
    /// JWT expiration time in seconds
    pub jwt_expiration_secs: u64,
    /// URL for the device identity signing service
    pub signing_service_url: String,
    /// Whether to validate JWT audience (default: false for compatibility)
    pub validate_jwt_audience: bool,
}

impl Default for OnesOidcConfig {
    fn default() -> Self {
        Self {
            http_timeout: Duration::from_secs(DEFAULT_HTTP_TIMEOUT_SECS),
            user_agent: Some(format!("ones-oidc/{}", env!("CARGO_PKG_VERSION"))),
            jwt_expiration_secs: DEFAULT_JWT_EXPIRATION_SECS,
            signing_service_url: DEFAULT_SIGNING_SERVICE_URL.to_string(),
            validate_jwt_audience: false,
        }
    }
}

impl OnesOidcConfig {
    /// Create a new configuration with custom HTTP timeout
    pub fn with_timeout(timeout: Duration) -> Self {
        Self {
            http_timeout: timeout,
            ..Default::default()
        }
    }

    /// Create a new configuration with custom signing service URL
    pub fn with_signing_service_url(url: String) -> Self {
        Self {
            signing_service_url: url,
            ..Default::default()
        }
    }

    /// Create a new configuration with JWT audience validation enabled
    pub fn with_jwt_audience_validation(enabled: bool) -> Self {
        Self {
            validate_jwt_audience: enabled,
            ..Default::default()
        }
    }

    /// Create a new configuration with custom JWT expiration time
    pub fn with_jwt_expiration(expiration_secs: u64) -> Self {
        Self {
            jwt_expiration_secs: expiration_secs,
            ..Default::default()
        }
    }

    /// Builder method to set HTTP timeout
    pub fn timeout(mut self, timeout: Duration) -> Self {
        self.http_timeout = timeout;
        self
    }

    /// Builder method to set signing service URL
    pub fn signing_service_url(mut self, url: String) -> Self {
        self.signing_service_url = url;
        self
    }

    /// Builder method to enable/disable JWT audience validation
    pub fn jwt_audience_validation(mut self, enabled: bool) -> Self {
        self.validate_jwt_audience = enabled;
        self
    }

    /// Builder method to set JWT expiration time
    pub fn jwt_expiration(mut self, expiration_secs: u64) -> Self {
        self.jwt_expiration_secs = expiration_secs;
        self
    }

    /// Create from environment variables
    pub fn from_env() -> Self {
        let mut config = Self::default();

        // HTTP timeout from environment
        if let Ok(timeout_str) = std::env::var("ONES_OIDC_TIMEOUT_SECS") {
            if let Ok(timeout_secs) = timeout_str.parse::<u64>() {
                config.http_timeout = Duration::from_secs(timeout_secs);
            }
        }

        // Signing service URL from environment
        if let Ok(signing_url) = std::env::var("ONES_OIDC_SIGNING_SERVICE_URL") {
            config.signing_service_url = signing_url;
        }

        // JWT audience validation from environment
        if let Ok(validate_str) = std::env::var("ONES_OIDC_VALIDATE_JWT_AUDIENCE") {
            config.validate_jwt_audience = validate_str.to_lowercase() == "true";
        }

        // JWT expiration from environment
        if let Ok(exp_str) = std::env::var("ONES_OIDC_JWT_EXPIRATION_SECS") {
            if let Ok(exp_secs) = exp_str.parse::<u64>() {
                config.jwt_expiration_secs = exp_secs;
            }
        }

        config
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_default_config() {
        let config = OnesOidcConfig::default();
        assert_eq!(config.jwt_expiration_secs, DEFAULT_JWT_EXPIRATION_SECS);
        assert_eq!(config.signing_service_url, DEFAULT_SIGNING_SERVICE_URL);
        assert!(!config.validate_jwt_audience);
    }

    #[test]
    fn test_builder_pattern() {
        let config = OnesOidcConfig::default()
            .timeout(Duration::from_secs(10))
            .signing_service_url("https://production.example.com/signature".to_string())
            .jwt_audience_validation(true)
            .jwt_expiration(600);

        assert_eq!(config.http_timeout, Duration::from_secs(10));
        assert_eq!(config.signing_service_url, "https://production.example.com/signature");
        assert!(config.validate_jwt_audience);
        assert_eq!(config.jwt_expiration_secs, 600);
    }

    #[test]
    fn test_from_env() {
        // Set environment variables
        std::env::set_var("ONES_OIDC_TIMEOUT_SECS", "5");
        std::env::set_var("ONES_OIDC_SIGNING_SERVICE_URL", "https://test.example.com/sign");
        std::env::set_var("ONES_OIDC_VALIDATE_JWT_AUDIENCE", "true");
        std::env::set_var("ONES_OIDC_JWT_EXPIRATION_SECS", "120");

        let config = OnesOidcConfig::from_env();

        assert_eq!(config.http_timeout, Duration::from_secs(5));
        assert_eq!(config.signing_service_url, "https://test.example.com/sign");
        assert!(config.validate_jwt_audience);
        assert_eq!(config.jwt_expiration_secs, 120);

        // Clean up environment variables
        std::env::remove_var("ONES_OIDC_TIMEOUT_SECS");
        std::env::remove_var("ONES_OIDC_SIGNING_SERVICE_URL");
        std::env::remove_var("ONES_OIDC_VALIDATE_JWT_AUDIENCE");
        std::env::remove_var("ONES_OIDC_JWT_EXPIRATION_SECS");
    }
}