1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
use std::ops::RangeInclusive;
use std::time::Duration;

use reqwest::Url;

#[derive(Clone)]
pub struct Config {
    /// Auth0 base url.
    /// This is the url used by the bridge to fetch new tokens
    pub token_url: Url,
    /// The microservice implementing this Bridge
    pub caller: String,
    /// The microservice I want to connect to.
    /// This should match the string defined in auth0 as "audience"
    pub audience: String,
    /// Cache configuration. Could be a redis connection string or inmemory cache
    pub cache_type: CacheType,
    /// The key to use in order to encrypt `CachedToken` in redis
    pub token_encryption_key: String,
    /// Every {check_interval} the `TokenDispenser` actor checks if token needs to be refreshed
    pub check_interval: Duration,
    /// this is a time period in which the token should be considered stale. Expressed as a range that represent the whole lifespan of the token.
    /// The lower bound means that from that from that moment onward the library will try to refresh the token at the scheduled time
    /// the higher bound means that the library will try to refresh the token as soon as possible
    pub staleness_check_percentage: StalenessCheckPercentage,
    /// Auth0 client identifier. Every machine should share the same identifier
    pub client_id: String,
    /// Auth0 client secret. Every machine should share the same secret
    pub client_secret: String,
    /// JWKS url
    /// This is the url that the bridge uses to fetch JWKS
    pub jwks_url: Url,
    /// Scope
    /// This is the scopes requested by the bridge when fetching tokens
    pub scope: Option<String>,
}

impl Config {
    pub fn token_url(&self) -> &Url {
        &self.token_url
    }

    pub fn caller(&self) -> &str {
        &self.caller
    }

    pub fn audience(&self) -> &str {
        &self.audience
    }

    pub fn cache_type(&self) -> &CacheType {
        &self.cache_type
    }

    pub fn token_encryption_key(&self) -> &str {
        &self.token_encryption_key
    }

    pub fn check_interval(&self) -> &Duration {
        &self.check_interval
    }

    pub fn client_id(&self) -> &str {
        &self.client_id
    }

    pub fn client_secret(&self) -> &str {
        &self.client_secret
    }

    pub fn jwks_url(&self) -> &Url {
        &self.jwks_url
    }

    pub fn staleness_check_percentage(&self) -> &StalenessCheckPercentage {
        &self.staleness_check_percentage
    }

    pub fn is_inmemory_cache(&self) -> bool {
        self.cache_type == CacheType::Inmemory
    }

    #[cfg(test)]
    pub fn test_config(server: &mockito::Server) -> Config {
        use std::str::FromStr;
        Config {
            token_url: Url::from_str(&format!("{}/{}", server.url().as_str(), "token")).unwrap(),
            jwks_url: Url::from_str(&format!("{}/{}", server.url().as_str(), "jwks")).unwrap(),
            caller: "caller".to_string(),
            audience: "audience".to_string(),
            cache_type: CacheType::Inmemory,
            token_encryption_key: "32char_long_token_encryption_key".to_string(),
            check_interval: Duration::from_secs(10),
            staleness_check_percentage: StalenessCheckPercentage::default(),
            client_id: "client_id".to_string(),
            client_secret: "client_secret".to_string(),
            scope: None,
        }
    }
}

// Eg. `Redis("redis://{host}:{port}?{ParamKey1}={ParamKey2}")` or `Inmemory` for inmemory cache
#[derive(Clone, Eq, PartialEq)]
pub enum CacheType {
    Redis(String),
    Inmemory,
}

impl CacheType {
    pub fn redis_connection_url(&self) -> &str {
        match &self {
            CacheType::Redis(url) => url,
            CacheType::Inmemory => {
                panic!("Something went wrong getting Redis connection string")
            }
        }
    }
}

#[derive(Clone)]
pub struct StalenessCheckPercentage(RangeInclusive<f64>);

impl StalenessCheckPercentage {
    pub fn new(min: f64, max: f64) -> Self {
        assert!((0.0..=1.0).contains(&min));
        assert!((0.0..=1.0).contains(&max));
        assert!(min <= max);

        Self(min..=max)
    }

    pub fn random_value_between(&self) -> f64 {
        use rand::Rng;
        rand::thread_rng().gen_range(self.0.clone())
    }
}

impl Default for StalenessCheckPercentage {
    fn default() -> Self {
        Self(0.6..=0.9)
    }
}

impl From<RangeInclusive<f64>> for StalenessCheckPercentage {
    fn from(range: RangeInclusive<f64>) -> Self {
        Self::new(*range.start(), *range.end())
    }
}