google_workspace_apis/auth/
client.rs1use std::{str::FromStr, sync::Arc};
2
3use anyhow::Error;
4use chrono::{DateTime, Utc};
5use schemars::JsonSchema;
6use serde::{Deserialize, Serialize};
7
8use super::refresh_acces_token;
9
10#[derive(Debug, JsonSchema, Clone, Default, Serialize, Deserialize)]
11pub struct AccessToken {
12 #[serde(
13 default,
14 skip_serializing_if = "String::is_empty",
15 deserialize_with = "crate::utils::deserialize::deserialize_nullable_string::deserialize"
16 )]
17 pub token_type: String,
18
19 #[serde(
20 default,
21 skip_serializing_if = "String::is_empty",
22 deserialize_with = "crate::utils::deserialize::deserialize_nullable_string::deserialize"
23 )]
24 pub access_token: String,
25 #[serde(default)]
26 pub expires_in: i64,
27
28 #[serde(
29 default,
30 skip_serializing_if = "String::is_empty",
31 deserialize_with = "crate::utils::deserialize::deserialize_nullable_string::deserialize"
32 )]
33 pub refresh_token: String,
34 #[serde(default, alias = "x_refresh_token_expires_in")]
35 pub refresh_token_expires_in: i64,
36
37 #[serde(
38 default,
39 skip_serializing_if = "String::is_empty",
40 deserialize_with = "crate::utils::deserialize::deserialize_nullable_string::deserialize"
41 )]
42 pub scope: String,
43}
44
45#[derive(Debug, JsonSchema, Clone, Default, Serialize, Deserialize)]
46pub struct ClientTokenData {
47 #[serde(
48 default,
49 skip_serializing_if = "String::is_empty",
50 deserialize_with = "crate::utils::deserialize::deserialize_nullable_string::deserialize"
51 )]
52 pub access_token: String,
53
54 #[serde(default)]
55 pub expires_on: chrono::DateTime<chrono::Utc>,
56
57 #[serde(
58 default,
59 skip_serializing_if = "String::is_empty",
60 deserialize_with = "crate::utils::deserialize::deserialize_nullable_string::deserialize"
61 )]
62 pub refresh_token: String,
63}
64
65#[derive(Debug, JsonSchema, Clone, Default, Serialize, Deserialize)]
66pub struct ClientCredentials {
67 #[serde(
68 default,
69 skip_serializing_if = "String::is_empty",
70 deserialize_with = "crate::utils::deserialize::deserialize_nullable_string::deserialize"
71 )]
72 pub client_id: String,
73
74 #[serde(
75 default,
76 skip_serializing_if = "String::is_empty",
77 deserialize_with = "crate::utils::deserialize::deserialize_nullable_string::deserialize"
78 )]
79 pub client_secret: String,
80
81 #[serde(
82 default,
83 skip_serializing_if = "String::is_empty",
84 deserialize_with = "crate::utils::deserialize::deserialize_nullable_string::deserialize"
85 )]
86 pub redirect_uri: String,
87
88 #[serde(
89 default,
90 skip_serializing_if = "String::is_empty",
91 deserialize_with = "crate::utils::deserialize::deserialize_nullable_string::deserialize"
92 )]
93 pub refresh_token: String,
94}
95
96#[derive(Clone, Default)]
97pub struct GoogleClient {
98 pub client_credentials: ClientCredentials,
99 pub access_token: Option<ClientTokenData>,
100 pub req_client: reqwest::Client,
101 pub auto_refresh_token: bool,
102 refresh_handlers: Vec<Arc<dyn TokenRefreshHandler>>,
103}
104
105impl std::fmt::Debug for GoogleClient {
106 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
107 f.debug_struct("GoogleClient")
108 .field("access_token", &self.access_token)
109 .field("refresh_token", &"[REDACTED]")
110 .field(
111 "token_expiry",
112 &self.access_token.as_ref().unwrap().expires_on,
113 )
114 .field("client_id", &self.client_credentials.client_id)
115 .field("client_secret", &"[REDACTED]")
116 .field(
117 "refresh_handlers",
118 &format!("[{} handlers]", self.refresh_handlers.len()),
119 )
120 .finish()
121 }
122}
123
124impl From<AccessToken> for ClientTokenData {
125 fn from(token: AccessToken) -> Self {
126 let now = chrono::Utc::now();
127 let new_expires_on = now + chrono::Duration::seconds(token.expires_in);
128 Self {
129 access_token: token.access_token,
130 expires_on: new_expires_on,
131 refresh_token: token.refresh_token,
132 }
133 }
134}
135
136impl GoogleClient {
137 pub fn new(
138 client_credentials: ClientCredentials,
139 access_token: AccessToken,
140 auto_refresh_token: bool,
141 ) -> Self {
142 let client = build_default_reqwest_client(&access_token.access_token);
143 Self {
144 client_credentials,
145 access_token: Some(access_token.into()),
146 req_client: client,
147 auto_refresh_token,
148 refresh_handlers: Vec::new(),
149 }
150 }
151
152 pub fn add_token_refresh_handler<H>(&mut self, handler: H)
153 where
154 H: TokenRefreshHandler + 'static,
155 {
156 self.refresh_handlers.push(Arc::new(handler));
157 }
158
159 pub async fn refresh_access_token_check(&mut self) -> Result<(), Error> {
160 if self.auto_refresh_token && !self.is_access_token_valid() {
161 self.update_access_token().await?;
162 }
163 Ok(())
164 }
165
166 pub fn enable_auto_refresh(&mut self) {
167 self.auto_refresh_token = true;
168 }
169
170 pub fn disable_auto_refresh(&mut self) {
171 self.auto_refresh_token = false;
172 }
173
174 pub fn is_access_token_valid(&self) -> bool {
175 if let Some(token_data) = &self.access_token {
176 let now = chrono::Utc::now();
177 return now < token_data.expires_on;
178 }
179 false
180 }
181
182 pub async fn update_access_token(&mut self) -> Result<(), Error> {
183 let new_token = refresh_acces_token(&self.client_credentials).await?;
184 self.access_token = Some(new_token.clone().into());
185 let client = build_default_reqwest_client(&new_token.access_token);
186 self.req_client = client;
187
188 for handler in &mut self.refresh_handlers {
189 handler.on_token_refresh(
190 self.access_token.as_ref().unwrap().access_token.clone(),
191 self.access_token.as_ref().unwrap().refresh_token.clone(),
192 self.access_token.as_ref().unwrap().expires_on,
193 );
194 }
195 Ok(())
196 }
197}
198
199pub trait TokenRefreshHandler: Send + Sync {
200 fn on_token_refresh(&self, new_token: String, refresh_token: String, new_expiry: DateTime<Utc>);
201}
202
203pub fn get_validity_token_secs(datetime_str: &str) -> i64 {
206 let now = chrono::Utc::now();
207
208 let saved_date = chrono::DateTime::<chrono::Utc>::from_str(datetime_str).unwrap();
209 let seconds_valid = saved_date - now;
210 seconds_valid.num_seconds()
211}
212
213fn build_default_reqwest_client(token: &str) -> reqwest::Client {
214 let mut headers = reqwest::header::HeaderMap::new();
215 headers.insert(
216 reqwest::header::AUTHORIZATION,
217 format!("Bearer {token}").parse().unwrap(),
218 );
219 headers.insert(reqwest::header::ACCEPT, "application/json".parse().unwrap());
220 headers.insert(
221 reqwest::header::CONTENT_TYPE,
222 "application/json".parse().unwrap(),
223 );
224 reqwest::Client::builder()
225 .default_headers(headers)
226 .build()
227 .expect("Failed to build reqwest client")
228}
229
230impl<F> TokenRefreshHandler for F
232where
233 F: Fn(String, String, DateTime<Utc>) + Send + Sync,
234{
235 fn on_token_refresh(
236 &self,
237 new_token: String,
238 refresh_token: String,
239 new_expiry: DateTime<Utc>,
240 ) {
241 self(new_token, refresh_token, new_expiry);
242 }
243}