jwt_verify/cognito/config.rs
1use std::collections::HashSet;
2use std::time::Duration;
3
4use crate::claims::ClaimValidator;
5use crate::common::error::JwtError;
6
7/// Token use types for Cognito JWT tokens.
8///
9/// This enum represents the different types of tokens that can be issued by AWS Cognito:
10/// - ID tokens (`id`): Used for authentication and contain user identity information
11/// - Access tokens (`access`): Used for authorization and contain scopes/permissions
12///
13/// # Examples
14///
15/// ```
16/// use jwt_verify::cognito::TokenUse;
17///
18/// // Create from string
19/// let token_use = TokenUse::from_str("id").unwrap();
20/// assert_eq!(token_use, TokenUse::Id);
21///
22/// // Convert to string
23/// assert_eq!(TokenUse::Access.as_str(), "access");
24/// ```
25#[derive(Debug, Clone, Copy, PartialEq, Eq)]
26pub enum TokenUse {
27 /// ID token - used for authentication and contains user identity information
28 Id,
29 /// Access token - used for authorization and contains scopes/permissions
30 Access,
31}
32
33impl TokenUse {
34 /// Get the string representation of the token use
35 pub fn as_str(&self) -> &'static str {
36 match self {
37 TokenUse::Id => "id",
38 TokenUse::Access => "access",
39 }
40 }
41
42 /// Create a TokenUse from a string
43 pub fn from_str(s: &str) -> Option<Self> {
44 match s {
45 "id" => Some(TokenUse::Id),
46 "access" => Some(TokenUse::Access),
47 _ => None,
48 }
49 }
50}
51
52/// Configuration for the Cognito JWT verification process.
53///
54/// This struct holds all configuration options for verifying Cognito JWT tokens,
55/// including AWS region, user pool ID, allowed client IDs, token types, clock skew,
56/// cache duration, required claims, custom validators, and error verbosity.
57///
58/// The configuration is immutable after creation, but can be modified using the builder
59/// pattern methods like `with_clock_skew`, `with_cache_duration`, etc.
60///
61/// # Examples
62///
63/// ```
64/// use jwt_verify::{VerifierConfig, StringValueValidator};
65/// use std::time::Duration;
66///
67/// // Create a basic configuration
68/// let config = VerifierConfig::new(
69/// "us-east-1",
70/// "us-east-1_example",
71/// &["client1".to_string()],
72/// None,
73/// ).unwrap();
74///
75/// // Add a custom validator
76/// let config = config.with_custom_validator(
77/// Box::new(StringValueValidator::new("app_id", "my-app"))
78/// );
79/// ```
80pub struct VerifierConfig {
81 /// AWS region where the Cognito user pool is located
82 pub region: String,
83 /// Cognito user pool ID in the format "region_poolid"
84 pub user_pool_id: String,
85 /// List of allowed client IDs for this user pool
86 pub client_ids: Vec<String>,
87 /// List of allowed token types (ID tokens, Access tokens)
88 pub allowed_token_uses: Vec<TokenUse>,
89 /// Clock skew tolerance for token expiration and issuance time validation
90 pub clock_skew: Duration,
91 /// Duration for which JWKs are cached before refreshing
92 pub jwk_cache_duration: Duration,
93 /// Set of claims that must be present and valid in the token
94 pub required_claims: HashSet<String>,
95 /// List of custom validators for additional claim validation
96 #[allow(clippy::type_complexity)]
97 pub custom_validators: Vec<Box<dyn ClaimValidator + Send + Sync>>,
98 /// Level of detail in error messages
99 pub error_verbosity: crate::common::error::ErrorVerbosity,
100}
101
102// Manual implementation of Debug for VerifierConfig since custom_validators doesn't implement Debug
103impl std::fmt::Debug for VerifierConfig {
104 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
105 f.debug_struct("VerifierConfig")
106 .field("region", &self.region)
107 .field("user_pool_id", &self.user_pool_id)
108 .field("client_ids", &self.client_ids)
109 .field("clock_skew", &self.clock_skew)
110 .field("jwk_cache_duration", &self.jwk_cache_duration)
111 .field("required_claims", &self.required_claims)
112 .field(
113 "custom_validators",
114 &format!("[{} validators]", self.custom_validators.len()),
115 )
116 .field("error_verbosity", &self.error_verbosity)
117 .finish()
118 }
119}
120
121// Manual implementation of Clone for VerifierConfig since custom_validators doesn't implement Clone
122impl Clone for VerifierConfig {
123 fn clone(&self) -> Self {
124 Self {
125 region: self.region.clone(),
126 user_pool_id: self.user_pool_id.clone(),
127 client_ids: self.client_ids.clone(),
128 allowed_token_uses: self.allowed_token_uses.clone(),
129 clock_skew: self.clock_skew,
130 jwk_cache_duration: self.jwk_cache_duration,
131 required_claims: self.required_claims.clone(),
132 custom_validators: Vec::new(), // Custom validators can't be cloned, so we create an empty vector
133 error_verbosity: self.error_verbosity,
134 }
135 }
136}
137
138impl VerifierConfig {
139 /// Create a new verifier configuration with validation for required parameters.
140 ///
141 /// This method creates a new `VerifierConfig` with the specified region, user pool ID,
142 /// and client IDs. It validates that the region and user pool ID are not empty and that
143 /// the user pool ID has the correct format.
144 ///
145 /// # Parameters
146 ///
147 /// * `region` - AWS region where the Cognito user pool is located (e.g., "us-east-1")
148 /// * `user_pool_id` - Cognito user pool ID in the format "region_poolid"
149 /// * `client_ids` - List of allowed client IDs for this user pool
150 /// * `token_uses` - Optional list of allowed token types (defaults to both ID and Access tokens)
151 ///
152 /// # Returns
153 ///
154 /// Returns a `Result` containing the new `VerifierConfig` if successful, or a `JwtError`
155 /// if validation fails.
156 ///
157 /// # Errors
158 ///
159 /// Returns a `JwtError::ConfigurationError` if:
160 /// - The region is empty
161 /// - The user pool ID is empty
162 /// - The user pool ID does not have the correct format (should contain an underscore)
163 ///
164 /// # Examples
165 ///
166 /// ```
167 /// use jwt_verify::VerifierConfig;
168 ///
169 /// // Create a basic configuration
170 /// let config = VerifierConfig::new(
171 /// "us-east-1",
172 /// "us-east-1_example",
173 /// &["client1".to_string()],
174 /// None,
175 /// ).unwrap();
176 /// ```
177 pub fn new(region: &str, user_pool_id: &str, client_ids: &[String], token_uses: Option<Vec<TokenUse>>,) -> Result<Self, JwtError> {
178 // Validate region
179 if region.is_empty() {
180 return Err(JwtError::ConfigurationError {
181 parameter: Some("region".to_string()),
182 error: "Region cannot be empty".to_string(),
183 });
184 }
185
186 // Validate user pool ID
187 if user_pool_id.is_empty() {
188 return Err(JwtError::ConfigurationError {
189 parameter: Some("user_pool_id".to_string()),
190 error: "User pool ID cannot be empty".to_string(),
191 });
192 }
193
194 // Validate user pool ID format
195 if !user_pool_id.contains('_') {
196 return Err(JwtError::ConfigurationError {
197 parameter: Some("user_pool_id".to_string()),
198 error: "Invalid user pool ID format. Expected format: region_poolid".to_string(),
199 });
200 }
201
202
203 let token_uses = match token_uses {
204 None => vec![TokenUse::Id, TokenUse::Access],
205 Some(tu) => tu,
206 };
207
208 Ok(Self {
209 region: region.to_string(),
210 user_pool_id: user_pool_id.to_string(),
211 client_ids: client_ids.to_vec(),
212 allowed_token_uses: token_uses, // Default: allow both ID and Access tokens
213 clock_skew: Duration::from_secs(60), // Default: 1 minute
214 jwk_cache_duration: Duration::from_secs(3600 * 24), // Default: 24 hours
215 required_claims: HashSet::from([
216 "sub".to_string(),
217 "iss".to_string(),
218 "client_id".to_string(),
219 "exp".to_string(),
220 "iat".to_string(),
221 // Removed "token_use" as it's validated separately
222 ]),
223 custom_validators: Vec::new(),
224 error_verbosity: crate::common::error::ErrorVerbosity::Standard,
225 })
226 }
227
228 /// Set clock skew tolerance for token validation.
229 ///
230 /// Clock skew is used to account for time differences between the token issuer
231 /// and the token verifier. This is important for validating token expiration
232 /// and issuance times.
233 ///
234 /// # Parameters
235 ///
236 /// * `skew` - The clock skew duration to allow (default: 60 seconds)
237 ///
238 /// # Returns
239 ///
240 /// Returns a new `VerifierConfig` with the updated clock skew.
241 ///
242 /// # Examples
243 ///
244 /// ```
245 /// use jwt_verify::VerifierConfig;
246 /// use std::time::Duration;
247 ///
248 /// let config = VerifierConfig::new("us-east-1", "us-east-1_example", &[], None)
249 /// .unwrap()
250 /// .with_clock_skew(Duration::from_secs(120)); // 2 minutes
251 /// ```
252 pub fn with_clock_skew(mut self, skew: Duration) -> Self {
253 self.clock_skew = skew;
254 self
255 }
256
257 /// Set JWK cache duration for key management.
258 ///
259 /// This determines how long JWKs (JSON Web Keys) are cached before being refreshed
260 /// from the Cognito endpoint. Longer durations reduce network requests but may
261 /// delay key rotation recognition.
262 ///
263 /// # Parameters
264 ///
265 /// * `duration` - The cache duration (default: 24 hours)
266 ///
267 /// # Returns
268 ///
269 /// Returns a new `VerifierConfig` with the updated cache duration.
270 ///
271 /// # Examples
272 ///
273 /// ```
274 /// use jwt_verify::VerifierConfig;
275 /// use std::time::Duration;
276 ///
277 /// let config = VerifierConfig::new("us-east-1", "us-east-1_example", &[], None)
278 /// .unwrap()
279 /// .with_cache_duration(Duration::from_secs(3600 * 12)); // 12 hours
280 /// ```
281 pub fn with_cache_duration(mut self, duration: Duration) -> Self {
282 self.jwk_cache_duration = duration;
283 self
284 }
285
286 /// Add a required claim to the validation process.
287 ///
288 /// Required claims must be present in the token and will be validated.
289 /// By default, the following claims are required: "sub", "iss", "client_id", "exp", "iat".
290 ///
291 /// # Parameters
292 ///
293 /// * `claim` - The name of the claim to require
294 ///
295 /// # Returns
296 ///
297 /// Returns a new `VerifierConfig` with the added required claim.
298 ///
299 /// # Examples
300 ///
301 /// ```
302 /// use jwt_verify::VerifierConfig;
303 ///
304 /// let config = VerifierConfig::new("us-east-1", "us-east-1_example", &[], None)
305 /// .unwrap()
306 /// .with_required_claim("custom_claim");
307 /// ```
308 pub fn with_required_claim(mut self, claim: &str) -> Self {
309 self.required_claims.insert(claim.to_string());
310 self
311 }
312
313 /// Add a custom validator for additional claim validation.
314 ///
315 /// Custom validators allow for application-specific validation logic beyond
316 /// the standard JWT claim validation. They can validate specific claim values,
317 /// formats, or relationships between claims.
318 ///
319 /// # Parameters
320 ///
321 /// * `validator` - A boxed implementation of the `ClaimValidator` trait
322 ///
323 /// # Returns
324 ///
325 /// Returns a new `VerifierConfig` with the added custom validator.
326 ///
327 /// # Examples
328 ///
329 /// ```
330 /// use jwt_verify::{VerifierConfig, StringValueValidator};
331 ///
332 /// let config = VerifierConfig::new("us-east-1", "us-east-1_example", &[], None)
333 /// .unwrap()
334 /// .with_custom_validator(Box::new(StringValueValidator::new(
335 /// "app_id", "my-application"
336 /// )));
337 /// ```
338 pub fn with_custom_validator(
339 mut self,
340 validator: Box<dyn ClaimValidator + Send + Sync>,
341 ) -> Self {
342 self.custom_validators.push(validator);
343 self
344 }
345
346 /// Set the error verbosity level for error reporting.
347 ///
348 /// This controls how much detail is included in error messages and logs.
349 /// Higher verbosity levels include more information but may expose sensitive data.
350 ///
351 /// # Parameters
352 ///
353 /// * `verbosity` - The error verbosity level (default: Standard)
354 ///
355 /// # Returns
356 ///
357 /// Returns a new `VerifierConfig` with the updated error verbosity.
358 ///
359 /// # Examples
360 ///
361 /// ```
362 /// use jwt_verify::{VerifierConfig, config::ErrorVerbosity};
363 ///
364 /// let config = VerifierConfig::new("us-east-1", "us-east-1_example", &[], None)
365 /// .unwrap()
366 /// .with_error_verbosity(ErrorVerbosity::Detailed);
367 /// ```
368 pub fn with_error_verbosity(mut self, verbosity: crate::common::error::ErrorVerbosity) -> Self {
369 self.error_verbosity = verbosity;
370 self
371 }
372
373 /// Get issuer URL
374 pub fn get_issuer_url(&self) -> String {
375 format!(
376 "https://cognito-idp.{}.amazonaws.com/{}",
377 self.region, self.user_pool_id
378 )
379 }
380
381 /// Get JWK URL
382 pub fn get_jwk_url(&self) -> String {
383 format!("{}{}", self.get_issuer_url(), "/.well-known/jwks.json")
384 }
385}