use std::time::Duration;
use oauth2::{AuthUrl, TokenUrl};
use crate::{
constant::{
DEFAULT_AUTH_URL, DEFAULT_ESI_URL, DEFAULT_JWT_AUDIENCE, DEFAULT_JWT_ISSUERS,
DEFAULT_TOKEN_URL,
},
error::{ConfigError, Error},
oauth2::jwk::cache::JwtKeyCacheConfig,
};
pub struct Config {
pub(crate) esi_url: String,
pub(crate) auth_url: AuthUrl,
pub(crate) token_url: TokenUrl,
pub(crate) jwt_key_cache_config: JwtKeyCacheConfig,
pub(crate) jwt_issuers: Vec<String>,
pub(crate) jwt_audience: String,
pub(crate) esi_validate_token_before_request: bool,
}
pub struct ConfigBuilder {
pub(crate) esi_url: String,
pub(crate) auth_url: String,
pub(crate) token_url: String,
pub(crate) jwt_key_cache_config: JwtKeyCacheConfig,
pub(crate) jwt_issuers: Vec<String>,
pub(crate) jwt_audience: String,
pub(crate) esi_validate_token_before_request: bool,
}
impl Config {
pub fn new() -> Result<Self, Error> {
ConfigBuilder::new().build()
}
pub fn builder() -> ConfigBuilder {
ConfigBuilder::new()
}
}
impl Default for ConfigBuilder {
fn default() -> Self {
Self::new()
}
}
impl ConfigBuilder {
pub fn new() -> Self {
let issuers: Vec<String> = DEFAULT_JWT_ISSUERS
.to_vec()
.iter()
.map(|str| str.to_string())
.collect();
Self {
esi_url: DEFAULT_ESI_URL.to_string(),
auth_url: DEFAULT_AUTH_URL.to_string(),
token_url: DEFAULT_TOKEN_URL.to_string(),
jwt_key_cache_config: JwtKeyCacheConfig::new(),
jwt_issuers: issuers,
jwt_audience: DEFAULT_JWT_AUDIENCE.to_string(),
esi_validate_token_before_request: true,
}
}
pub fn build(self) -> Result<Config, Error> {
if self.jwt_key_cache_config.background_refresh_threshold == 0 {
return Err(Error::ConfigError(
ConfigError::InvalidBackgroundRefreshThreshold,
));
}
if self.jwt_key_cache_config.background_refresh_threshold >= 100 {
return Err(Error::ConfigError(
ConfigError::InvalidBackgroundRefreshThreshold,
));
}
let auth_url = match AuthUrl::new(self.auth_url.clone()) {
Ok(url) => url,
Err(_) => return Err(Error::ConfigError(ConfigError::InvalidAuthUrl)),
};
let token_url = match TokenUrl::new(self.token_url.clone()) {
Ok(url) => url,
Err(_) => {
return Err(Error::ConfigError(ConfigError::InvalidTokenUrl));
}
};
Ok(Config {
esi_url: self.esi_url,
auth_url,
token_url,
jwt_key_cache_config: self.jwt_key_cache_config,
jwt_issuers: self.jwt_issuers,
jwt_audience: self.jwt_audience,
esi_validate_token_before_request: self.esi_validate_token_before_request,
})
}
pub fn esi_url(mut self, esi_url: &str) -> Self {
self.esi_url = esi_url.to_string();
self
}
pub fn auth_url(mut self, auth_url: &str) -> Self {
self.auth_url = auth_url.to_string();
self
}
pub fn token_url(mut self, token_url: &str) -> Self {
self.token_url = token_url.to_string();
self
}
pub fn jwk_url(mut self, jwk_url: &str) -> Self {
self.jwt_key_cache_config.jwk_url = jwk_url.to_string();
self
}
pub fn jwk_cache_ttl(mut self, duration: Duration) -> Self {
self.jwt_key_cache_config.cache_ttl = duration;
self
}
pub fn jwk_refresh_backoff(mut self, duration: Duration) -> Self {
self.jwt_key_cache_config.refresh_backoff = duration;
self
}
pub fn jwk_refresh_timeout(mut self, duration: Duration) -> Self {
self.jwt_key_cache_config.refresh_timeout = duration;
self
}
pub fn jwk_refresh_cooldown(mut self, duration: Duration) -> Self {
self.jwt_key_cache_config.refresh_cooldown = duration;
self
}
pub fn jwk_refresh_max_retries(mut self, retry_attempts: u32) -> Self {
self.jwt_key_cache_config.refresh_max_retries = retry_attempts;
self
}
pub fn jwk_background_refresh_enabled(mut self, background_refresh_enabled: bool) -> Self {
self.jwt_key_cache_config.background_refresh_enabled = background_refresh_enabled;
self
}
pub fn jwk_background_refresh_threshold(mut self, threshold_percentage: u64) -> Self {
self.jwt_key_cache_config.background_refresh_threshold = threshold_percentage;
self
}
pub fn jwt_issuers(mut self, issuers: Vec<String>) -> Self {
self.jwt_issuers = issuers;
self
}
pub fn jwt_audience(mut self, audience: &str) -> Self {
self.jwt_audience = audience.to_string();
self
}
pub fn esi_validate_token_before_request(mut self, enabled: bool) -> Self {
self.esi_validate_token_before_request = enabled;
self
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_default_config_setter_methods() {
let zero_seconds = Duration::from_secs(0);
let config = ConfigBuilder::default()
.auth_url("https://example.com")
.token_url("https://example.com")
.jwk_url("https://example.com")
.jwk_cache_ttl(zero_seconds)
.jwk_refresh_backoff(zero_seconds)
.jwk_refresh_timeout(zero_seconds)
.jwk_refresh_cooldown(zero_seconds)
.jwk_refresh_max_retries(0)
.jwk_background_refresh_enabled(false)
.jwk_background_refresh_threshold(1)
.jwt_issuers(vec!["example".to_string()])
.jwt_audience("example")
.esi_validate_token_before_request(false)
.build()
.expect("Failed to build Config");
let auth_url = AuthUrl::new("https://example.com".to_string()).unwrap();
let token_url = TokenUrl::new("https://example.com".to_string()).unwrap();
assert_eq!(config.auth_url, auth_url);
assert_eq!(config.token_url, token_url);
assert_eq!(config.jwt_key_cache_config.jwk_url, "https://example.com");
assert_eq!(config.jwt_key_cache_config.cache_ttl, zero_seconds);
assert_eq!(config.jwt_key_cache_config.refresh_backoff, zero_seconds);
assert_eq!(config.jwt_key_cache_config.refresh_timeout, zero_seconds);
assert_eq!(config.jwt_key_cache_config.refresh_cooldown, zero_seconds);
assert_eq!(config.jwt_key_cache_config.refresh_max_retries, 0);
assert_eq!(
config.jwt_key_cache_config.background_refresh_enabled,
false
);
assert_eq!(config.jwt_key_cache_config.background_refresh_threshold, 1);
assert_eq!(config.jwt_issuers, vec!["example"]);
assert_eq!(config.jwt_audience, "example");
assert_eq!(config.esi_validate_token_before_request, false)
}
#[test]
fn test_invalid_background_refresh_threshold_0() {
let result = Config::builder()
.jwk_background_refresh_threshold(0)
.build();
assert!(result.is_err());
assert!(matches!(
result,
Err(Error::ConfigError(
ConfigError::InvalidBackgroundRefreshThreshold
))
))
}
#[test]
fn test_invalid_background_refresh_threshold_100() {
let result = Config::builder()
.jwk_background_refresh_threshold(100)
.build();
assert!(result.is_err());
assert!(matches!(
result,
Err(Error::ConfigError(
ConfigError::InvalidBackgroundRefreshThreshold
))
))
}
#[test]
fn test_invalid_auth_url() {
let result = Config::builder().auth_url("invalid_url").build();
assert!(result.is_err());
assert!(matches!(
result,
Err(Error::ConfigError(ConfigError::InvalidAuthUrl))
));
}
#[test]
fn test_invalid_token_url() {
let result = Config::builder().token_url("invalid_url").build();
assert!(result.is_err());
assert!(matches!(
result,
Err(Error::ConfigError(ConfigError::InvalidTokenUrl))
));
}
}