ones_oidc/
config.rs

1use std::time::Duration;
2
3/// Default JWT expiration time in seconds
4const DEFAULT_JWT_EXPIRATION_SECS: u64 = 300;
5
6/// Default signing service URL (localhost for development)
7const DEFAULT_SIGNING_SERVICE_URL: &str = "http://localhost:8000/signature";
8
9/// Default HTTP timeout in seconds
10const DEFAULT_HTTP_TIMEOUT_SECS: u64 = 3;
11
12/// Default max discovery retries
13const DEFAULT_MAX_DISCOVERY_RETRIES: u32 = 10;
14
15/// Default initial backoff for discovery retries in seconds
16const DEFAULT_DISCOVERY_BACKOFF_SECS: u64 = 2;
17
18/// Configuration for the ONES OIDC client
19#[derive(Debug, Clone)]
20pub struct OnesOidcConfig {
21    /// HTTP timeout duration
22    pub http_timeout: Duration,
23    /// User agent string for HTTP requests
24    pub user_agent: Option<String>,
25    /// JWT expiration time in seconds
26    pub jwt_expiration_secs: u64,
27    /// URL for the device identity signing service
28    pub signing_service_url: String,
29    /// Whether to validate JWT audience (default: false for compatibility)
30    pub validate_jwt_audience: bool,
31    /// Maximum number of retries for OIDC discovery (0 = no retries)
32    pub max_discovery_retries: u32,
33    /// Initial backoff duration for discovery retries (doubles each retry)
34    pub discovery_backoff: Duration,
35}
36
37impl Default for OnesOidcConfig {
38    fn default() -> Self {
39        Self {
40            http_timeout: Duration::from_secs(DEFAULT_HTTP_TIMEOUT_SECS),
41            user_agent: Some(format!("ones-oidc/{}", env!("CARGO_PKG_VERSION"))),
42            jwt_expiration_secs: DEFAULT_JWT_EXPIRATION_SECS,
43            signing_service_url: DEFAULT_SIGNING_SERVICE_URL.to_string(),
44            validate_jwt_audience: false,
45            max_discovery_retries: DEFAULT_MAX_DISCOVERY_RETRIES,
46            discovery_backoff: Duration::from_secs(DEFAULT_DISCOVERY_BACKOFF_SECS),
47        }
48    }
49}
50
51impl OnesOidcConfig {
52    /// Create a new configuration with custom HTTP timeout
53    pub fn with_timeout(timeout: Duration) -> Self {
54        Self {
55            http_timeout: timeout,
56            ..Default::default()
57        }
58    }
59
60    /// Create a new configuration with custom signing service URL
61    pub fn with_signing_service_url(url: String) -> Self {
62        Self {
63            signing_service_url: url,
64            ..Default::default()
65        }
66    }
67
68    /// Create a new configuration with JWT audience validation enabled
69    pub fn with_jwt_audience_validation(enabled: bool) -> Self {
70        Self {
71            validate_jwt_audience: enabled,
72            ..Default::default()
73        }
74    }
75
76    /// Create a new configuration with custom JWT expiration time
77    pub fn with_jwt_expiration(expiration_secs: u64) -> Self {
78        Self {
79            jwt_expiration_secs: expiration_secs,
80            ..Default::default()
81        }
82    }
83
84    /// Builder method to set HTTP timeout
85    pub fn timeout(mut self, timeout: Duration) -> Self {
86        self.http_timeout = timeout;
87        self
88    }
89
90    /// Builder method to set signing service URL
91    pub fn signing_service_url(mut self, url: String) -> Self {
92        self.signing_service_url = url;
93        self
94    }
95
96    /// Builder method to enable/disable JWT audience validation
97    pub fn jwt_audience_validation(mut self, enabled: bool) -> Self {
98        self.validate_jwt_audience = enabled;
99        self
100    }
101
102    /// Builder method to set JWT expiration time
103    pub fn jwt_expiration(mut self, expiration_secs: u64) -> Self {
104        self.jwt_expiration_secs = expiration_secs;
105        self
106    }
107
108    /// Builder method to set max discovery retries
109    pub fn max_discovery_retries(mut self, retries: u32) -> Self {
110        self.max_discovery_retries = retries;
111        self
112    }
113
114    /// Builder method to set discovery backoff duration
115    pub fn discovery_backoff(mut self, backoff: Duration) -> Self {
116        self.discovery_backoff = backoff;
117        self
118    }
119
120    /// Create from environment variables
121    pub fn from_env() -> Self {
122        let mut config = Self::default();
123
124        // HTTP timeout from environment
125        if let Ok(timeout_str) = std::env::var("ONES_OIDC_TIMEOUT_SECS") {
126            if let Ok(timeout_secs) = timeout_str.parse::<u64>() {
127                config.http_timeout = Duration::from_secs(timeout_secs);
128            }
129        }
130
131        // Signing service URL from environment
132        if let Ok(signing_url) = std::env::var("ONES_OIDC_SIGNING_SERVICE_URL") {
133            config.signing_service_url = signing_url;
134        }
135
136        // JWT audience validation from environment
137        if let Ok(validate_str) = std::env::var("ONES_OIDC_VALIDATE_JWT_AUDIENCE") {
138            config.validate_jwt_audience = validate_str.to_lowercase() == "true";
139        }
140
141        // JWT expiration from environment
142        if let Ok(exp_str) = std::env::var("ONES_OIDC_JWT_EXPIRATION_SECS") {
143            if let Ok(exp_secs) = exp_str.parse::<u64>() {
144                config.jwt_expiration_secs = exp_secs;
145            }
146        }
147
148        // Max discovery retries from environment
149        if let Ok(retries_str) = std::env::var("ONES_OIDC_MAX_DISCOVERY_RETRIES") {
150            if let Ok(retries) = retries_str.parse::<u32>() {
151                config.max_discovery_retries = retries;
152            }
153        }
154
155        // Discovery backoff from environment
156        if let Ok(backoff_str) = std::env::var("ONES_OIDC_DISCOVERY_BACKOFF_SECS") {
157            if let Ok(backoff_secs) = backoff_str.parse::<u64>() {
158                config.discovery_backoff = Duration::from_secs(backoff_secs);
159            }
160        }
161
162        config
163    }
164}
165
166#[cfg(test)]
167mod tests {
168    use super::*;
169
170    #[test]
171    fn test_default_config() {
172        let config = OnesOidcConfig::default();
173        assert_eq!(config.jwt_expiration_secs, DEFAULT_JWT_EXPIRATION_SECS);
174        assert_eq!(config.signing_service_url, DEFAULT_SIGNING_SERVICE_URL);
175        assert!(!config.validate_jwt_audience);
176        assert_eq!(config.max_discovery_retries, DEFAULT_MAX_DISCOVERY_RETRIES);
177        assert_eq!(config.discovery_backoff, Duration::from_secs(DEFAULT_DISCOVERY_BACKOFF_SECS));
178    }
179
180    #[test]
181    fn test_builder_pattern() {
182        let config = OnesOidcConfig::default()
183            .timeout(Duration::from_secs(10))
184            .signing_service_url("https://production.example.com/signature".to_string())
185            .jwt_audience_validation(true)
186            .jwt_expiration(600)
187            .max_discovery_retries(5)
188            .discovery_backoff(Duration::from_secs(3));
189
190        assert_eq!(config.http_timeout, Duration::from_secs(10));
191        assert_eq!(config.signing_service_url, "https://production.example.com/signature");
192        assert!(config.validate_jwt_audience);
193        assert_eq!(config.jwt_expiration_secs, 600);
194        assert_eq!(config.max_discovery_retries, 5);
195        assert_eq!(config.discovery_backoff, Duration::from_secs(3));
196    }
197
198    #[test]
199    fn test_from_env() {
200        // Set environment variables
201        std::env::set_var("ONES_OIDC_TIMEOUT_SECS", "5");
202        std::env::set_var("ONES_OIDC_SIGNING_SERVICE_URL", "https://test.example.com/sign");
203        std::env::set_var("ONES_OIDC_VALIDATE_JWT_AUDIENCE", "true");
204        std::env::set_var("ONES_OIDC_JWT_EXPIRATION_SECS", "120");
205        std::env::set_var("ONES_OIDC_MAX_DISCOVERY_RETRIES", "3");
206        std::env::set_var("ONES_OIDC_DISCOVERY_BACKOFF_SECS", "5");
207
208        let config = OnesOidcConfig::from_env();
209
210        assert_eq!(config.http_timeout, Duration::from_secs(5));
211        assert_eq!(config.signing_service_url, "https://test.example.com/sign");
212        assert!(config.validate_jwt_audience);
213        assert_eq!(config.jwt_expiration_secs, 120);
214        assert_eq!(config.max_discovery_retries, 3);
215        assert_eq!(config.discovery_backoff, Duration::from_secs(5));
216
217        // Clean up environment variables
218        std::env::remove_var("ONES_OIDC_TIMEOUT_SECS");
219        std::env::remove_var("ONES_OIDC_SIGNING_SERVICE_URL");
220        std::env::remove_var("ONES_OIDC_VALIDATE_JWT_AUDIENCE");
221        std::env::remove_var("ONES_OIDC_JWT_EXPIRATION_SECS");
222        std::env::remove_var("ONES_OIDC_MAX_DISCOVERY_RETRIES");
223        std::env::remove_var("ONES_OIDC_DISCOVERY_BACKOFF_SECS");
224    }
225}