1use std::fmt::{Display, Formatter};
2
3#[async_trait::async_trait]
5pub trait TokenCache: Sync {
6 async fn token_and_exp(&self) -> Option<(String, u64)>;
9
10 async fn set_token(&self, token: String, exp: u64) -> crate::Result<()>;
12
13 async fn scope(&self) -> String;
15
16 async fn get(&self, client: &reqwest::Client) -> crate::Result<String> {
19 match self.token_and_exp().await {
20 Some((token, exp)) if now() + 300 < exp => Ok(token),
21 _ => {
22 let (token, exp) = self.fetch_token(client).await?;
23 self.set_token(token, exp).await?;
24
25 self.token_and_exp()
26 .await
27 .map(|(t, _)| t)
28 .ok_or(crate::Error::Other("Token is not set".to_string()))
29 }
30 }
31 }
32
33 async fn fetch_token(&self, client: &reqwest::Client) -> crate::Result<(String, u64)>;
35}
36
37#[derive(serde::Serialize)]
38struct Claims {
39 iss: String,
40 scope: String,
41 aud: String,
42 exp: u64,
43 iat: u64,
44}
45
46#[derive(serde::Deserialize, Debug)]
47struct TokenResponse {
49 access_token: String,
50 expires_in: u64,
51 }
53
54pub struct Token {
56 token: tokio::sync::RwLock<Option<DefaultTokenData>>,
59 access_scope: String,
61}
62
63#[derive(Debug, Clone)]
64pub struct DefaultTokenData(String, u64);
65
66impl Display for DefaultTokenData {
67 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
68 write!(f, "{}", self.0)
69 }
70}
71
72impl Default for Token {
73 fn default() -> Self {
74 Token::new("https://www.googleapis.com/auth/devstorage.full_control")
75 }
76}
77
78impl Token {
79 pub(crate) fn new(scope: &str) -> Self {
80 Self {
81 token: tokio::sync::RwLock::new(None),
82 access_scope: scope.to_string(),
83 }
84 }
85}
86
87#[async_trait::async_trait]
88impl TokenCache for Token {
89 async fn scope(&self) -> String {
90 self.access_scope.clone()
91 }
92
93 async fn token_and_exp(&self) -> Option<(String, u64)> {
94 self.token.read().await.as_ref().map(|d| (d.0.clone(), d.1))
95 }
96
97 async fn set_token(&self, token: String, exp: u64) -> crate::Result<()> {
98 *self.token.write().await = Some(DefaultTokenData(token, exp));
99 Ok(())
100 }
101
102 async fn fetch_token(&self, client: &reqwest::Client) -> crate::Result<(String, u64)> {
103 let now = now();
104 let exp = now + 3600;
105
106 let claims = Claims {
107 iss: crate::SERVICE_ACCOUNT.client_email.clone(),
108 scope: self.scope().await.into(),
109 aud: "https://www.googleapis.com/oauth2/v4/token".to_string(),
110 exp,
111 iat: now,
112 };
113 let header = jsonwebtoken::Header {
114 alg: jsonwebtoken::Algorithm::RS256,
115 ..Default::default()
116 };
117 let private_key_bytes = crate::SERVICE_ACCOUNT.private_key.as_bytes();
118 let private_key = jsonwebtoken::EncodingKey::from_rsa_pem(private_key_bytes)?;
119 let jwt = jsonwebtoken::encode(&header, &claims, &private_key)?;
120 let body = [
121 ("grant_type", "urn:ietf:params:oauth:grant-type:jwt-bearer"),
122 ("assertion", &jwt),
123 ];
124 let response: TokenResponse = client
125 .post("https://www.googleapis.com/oauth2/v4/token")
126 .form(&body)
127 .send()
128 .await?
129 .json()
130 .await?;
131 Ok((response.access_token, now + response.expires_in))
132 }
133}
134
135fn now() -> u64 {
136 std::time::SystemTime::now()
137 .duration_since(std::time::SystemTime::UNIX_EPOCH)
138 .unwrap()
139 .as_secs()
140}