1#![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
61pub 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 pub async fn reset_oauth2_v3_certs() -> Result<(), reqwest::Error> {
88 {
89 OAUTH_CERTS.write().await.value = None;
90 }
91 Ok(())
92 }
93
94 static OAUTH_CERTS: RwLock<Certs> = RwLock::const_new(Certs {
96 value: None,
97 expires_in: None,
98 });
99
100 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 {
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}