use std::collections::HashSet;
use std::time::Duration;
use crate::claims::ClaimValidator;
use crate::common::error::JwtError;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum TokenUse {
Id,
Access,
}
impl TokenUse {
pub fn as_str(&self) -> &'static str {
match self {
TokenUse::Id => "id",
TokenUse::Access => "access",
}
}
pub fn from_str(s: &str) -> Option<Self> {
match s {
"id" => Some(TokenUse::Id),
"access" => Some(TokenUse::Access),
_ => None,
}
}
}
pub struct VerifierConfig {
pub region: String,
pub user_pool_id: String,
pub client_ids: Vec<String>,
pub allowed_token_uses: Vec<TokenUse>,
pub clock_skew: Duration,
pub jwk_cache_duration: Duration,
pub required_claims: HashSet<String>,
#[allow(clippy::type_complexity)]
pub custom_validators: Vec<Box<dyn ClaimValidator + Send + Sync>>,
pub error_verbosity: crate::common::error::ErrorVerbosity,
}
impl std::fmt::Debug for VerifierConfig {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("VerifierConfig")
.field("region", &self.region)
.field("user_pool_id", &self.user_pool_id)
.field("client_ids", &self.client_ids)
.field("clock_skew", &self.clock_skew)
.field("jwk_cache_duration", &self.jwk_cache_duration)
.field("required_claims", &self.required_claims)
.field(
"custom_validators",
&format!("[{} validators]", self.custom_validators.len()),
)
.field("error_verbosity", &self.error_verbosity)
.finish()
}
}
impl Clone for VerifierConfig {
fn clone(&self) -> Self {
Self {
region: self.region.clone(),
user_pool_id: self.user_pool_id.clone(),
client_ids: self.client_ids.clone(),
allowed_token_uses: self.allowed_token_uses.clone(),
clock_skew: self.clock_skew,
jwk_cache_duration: self.jwk_cache_duration,
required_claims: self.required_claims.clone(),
custom_validators: Vec::new(), error_verbosity: self.error_verbosity,
}
}
}
impl VerifierConfig {
pub fn new(region: &str, user_pool_id: &str, client_ids: &[String], token_uses: Option<Vec<TokenUse>>,) -> Result<Self, JwtError> {
if region.is_empty() {
return Err(JwtError::ConfigurationError {
parameter: Some("region".to_string()),
error: "Region cannot be empty".to_string(),
});
}
if user_pool_id.is_empty() {
return Err(JwtError::ConfigurationError {
parameter: Some("user_pool_id".to_string()),
error: "User pool ID cannot be empty".to_string(),
});
}
if !user_pool_id.contains('_') {
return Err(JwtError::ConfigurationError {
parameter: Some("user_pool_id".to_string()),
error: "Invalid user pool ID format. Expected format: region_poolid".to_string(),
});
}
let token_uses = match token_uses {
None => vec![TokenUse::Id, TokenUse::Access],
Some(tu) => tu,
};
Ok(Self {
region: region.to_string(),
user_pool_id: user_pool_id.to_string(),
client_ids: client_ids.to_vec(),
allowed_token_uses: token_uses, clock_skew: Duration::from_secs(60), jwk_cache_duration: Duration::from_secs(3600 * 24), required_claims: HashSet::from([
"sub".to_string(),
"iss".to_string(),
"client_id".to_string(),
"exp".to_string(),
"iat".to_string(),
]),
custom_validators: Vec::new(),
error_verbosity: crate::common::error::ErrorVerbosity::Standard,
})
}
pub fn with_clock_skew(mut self, skew: Duration) -> Self {
self.clock_skew = skew;
self
}
pub fn with_cache_duration(mut self, duration: Duration) -> Self {
self.jwk_cache_duration = duration;
self
}
pub fn with_required_claim(mut self, claim: &str) -> Self {
self.required_claims.insert(claim.to_string());
self
}
pub fn with_custom_validator(
mut self,
validator: Box<dyn ClaimValidator + Send + Sync>,
) -> Self {
self.custom_validators.push(validator);
self
}
pub fn with_error_verbosity(mut self, verbosity: crate::common::error::ErrorVerbosity) -> Self {
self.error_verbosity = verbosity;
self
}
pub fn get_issuer_url(&self) -> String {
format!(
"https://cognito-idp.{}.amazonaws.com/{}",
self.region, self.user_pool_id
)
}
pub fn get_jwk_url(&self) -> String {
format!("{}{}", self.get_issuer_url(), "/.well-known/jwks.json")
}
}