oauth_certs/
lib.rs

1//! The project fetches oauth certificates from providers during build time and stores them as lazy structures for retrieval.
2
3#![deny(
4    warnings,
5    bad_style,
6    dead_code,
7    improper_ctypes,
8    non_shorthand_field_patterns,
9    no_mangle_generic_items,
10    overflowing_literals,
11    path_statements,
12    patterns_in_fns_without_body,
13    unconditional_recursion,
14    unused,
15    unused_allocation,
16    unused_comparisons,
17    unused_parens,
18    while_true,
19    missing_debug_implementations,
20    missing_docs,
21    trivial_casts,
22    trivial_numeric_casts,
23    unused_extern_crates,
24    unused_import_braces,
25    unused_qualifications,
26    unused_results,
27    trivial_numeric_casts,
28    unreachable_pub,
29    unused_extern_crates,
30    unused_import_braces,
31    unused_qualifications,
32    unused_results,
33    deprecated,
34    unconditional_recursion,
35    unknown_lints,
36    unreachable_code,
37    unused_mut
38)]
39
40use jsonwebtoken::jwk::JwkSet;
41use std::time::Duration;
42use std::time::{SystemTime, UNIX_EPOCH};
43
44struct Certs {
45    value: Option<JwkSet>,
46    expires_in: Option<Duration>,
47}
48
49impl Certs {}
50
51fn time_now() -> Duration {
52    SystemTime::now()
53        .duration_since(UNIX_EPOCH)
54        .expect("Time went backwards")
55}
56
57fn is_expired(expires_in: Duration) -> bool {
58    time_now() > expires_in
59}
60
61/// Google certificates
62pub mod google {
63    use super::*;
64    use cache_control::CacheControl;
65    use tokio::sync::RwLock;
66
67    use crate::Certs;
68
69    async fn retrieve() -> Result<(JwkSet, Option<Duration>), reqwest::Error> {
70        let response = reqwest::get("https://www.googleapis.com/oauth2/v3/certs").await?;
71
72        let max_age = response
73            .headers()
74            .get("Cache-Control")
75            .map(|value| value.to_str().ok())
76            .flatten()
77            .map(|value| CacheControl::from_value(value))
78            .flatten()
79            .map(|value| value.max_age)
80            .flatten();
81
82        let jwk_set = response.json().await?;
83        Ok((jwk_set, max_age))
84    }
85
86    /// Reset Google oauth certificates
87    pub async fn reset_oauth2_v3_certs() -> Result<(), reqwest::Error> {
88        {
89            OAUTH_CERTS.write().await.value = None;
90        }
91        Ok(())
92    }
93
94    /// Google oauth certificates"
95    static OAUTH_CERTS: RwLock<Certs> = RwLock::const_new(Certs {
96        value: None,
97        expires_in: None,
98    });
99
100    /// Get JwkSet of Google oauth certificates
101    pub async fn oauth2_v3_certs() -> Result<JwkSet, reqwest::Error> {
102        {
103            let oauth_certs = OAUTH_CERTS.read().await;
104
105            if let Some(ref certs) = oauth_certs.value {
106                if !oauth_certs.expires_in.map(is_expired).unwrap_or(false) {
107                    return Ok(certs.clone());
108                }
109            }
110        }
111
112        let (certs, max_age) = retrieve().await?;
113        let expires_in = time_now() + max_age.unwrap_or(Duration::from_secs(1800));
114
115        // Drop lock ASAP
116        {
117            let mut v = OAUTH_CERTS.write().await;
118            v.value = Some(certs.clone());
119            v.expires_in = Some(expires_in)
120        }
121
122        Ok(certs)
123    }
124}
125
126#[cfg(test)]
127mod tests {
128    use super::*;
129
130    #[tokio::test]
131    async fn test_google_oauth_certs() {
132        assert!(google::oauth2_v3_certs().await.is_ok());
133    }
134}