1use crate::error::{AuthErrorOr, Error};
2
3use serde::{Deserialize, Serialize};
4use time::OffsetDateTime;
5
6#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Deserialize, Serialize)]
9pub struct AccessToken {
10 access_token: Option<String>,
11 expires_at: Option<OffsetDateTime>,
12}
13
14impl AccessToken {
15 pub fn token(&self) -> Option<&str> {
17 self.access_token.as_deref()
18 }
19
20 pub fn expiration_time(&self) -> Option<OffsetDateTime> {
22 self.expires_at
23 }
24
25 pub fn is_expired(&self) -> bool {
30 self.expires_at
32 .map(|expiration_time| {
33 expiration_time - time::Duration::minutes(1) <= OffsetDateTime::now_utc()
34 })
35 .unwrap_or(false)
36 }
37}
38
39impl From<TokenInfo> for AccessToken {
40 fn from(
41 TokenInfo {
42 access_token,
43 expires_at,
44 ..
45 }: TokenInfo,
46 ) -> Self {
47 AccessToken {
48 access_token,
49 expires_at,
50 }
51 }
52}
53
54#[derive(Clone, PartialEq, Debug, Deserialize, Serialize)]
59pub struct TokenInfo {
60 pub access_token: Option<String>,
62 pub refresh_token: Option<String>,
64 pub expires_at: Option<OffsetDateTime>,
66 pub id_token: Option<String>,
71}
72
73impl TokenInfo {
74 pub(crate) fn from_json(json_data: &[u8]) -> Result<TokenInfo, Error> {
75 #[derive(Deserialize)]
76 struct RawToken {
77 access_token: Option<String>,
78 refresh_token: Option<String>,
79 token_type: Option<String>,
80 expires_in: Option<i64>,
81 id_token: Option<String>,
82 }
83
84 let raw_token = serde_json::from_slice::<serde_json::Value>(json_data)?;
87 let RawToken {
88 access_token,
89 refresh_token,
90 token_type,
91 expires_in,
92 id_token,
93 } = <AuthErrorOr<RawToken>>::deserialize(raw_token)?.into_result()?;
94
95 match token_type {
96 Some(token_ty) if !token_ty.eq_ignore_ascii_case("bearer") => {
97 use std::io;
98 return Err(io::Error::new(
99 io::ErrorKind::InvalidData,
100 format!(
101 r#"unknown token type returned; expected "bearer" found {}"#,
102 token_ty
103 ),
104 )
105 .into());
106 }
107 _ => (),
108 }
109
110 let expires_at = match expires_in {
111 Some(seconds_from_now) => {
112 Some(OffsetDateTime::now_utc() + time::Duration::seconds(seconds_from_now))
113 }
114 None if id_token.is_some() && access_token.is_none() => {
115 Some(OffsetDateTime::now_utc() + time::Duration::HOUR)
120 }
121 None => None,
122 };
123
124 Ok(TokenInfo {
125 id_token,
126 access_token,
127 refresh_token,
128 expires_at,
129 })
130 }
131
132 pub fn is_expired(&self) -> bool {
134 self.expires_at
135 .map(|expiration_time| {
136 expiration_time - time::Duration::minutes(1) <= OffsetDateTime::now_utc()
137 })
138 .unwrap_or(false)
139 }
140}
141
142#[derive(Deserialize, Serialize, Clone, Default, Debug)]
145pub struct ApplicationSecret {
146 pub client_id: String,
148 pub client_secret: String,
150 pub token_uri: String,
152 pub auth_uri: String,
154 pub redirect_uris: Vec<String>,
156 pub project_id: Option<String>,
158 pub client_email: Option<String>,
160 pub auth_provider_x509_cert_url: Option<String>,
163 pub client_x509_cert_url: Option<String>,
165}
166
167#[derive(Deserialize, Serialize, Default, Debug)]
170pub struct ConsoleApplicationSecret {
171 pub web: Option<ApplicationSecret>,
173 pub installed: Option<ApplicationSecret>,
175}
176
177#[cfg(test)]
178pub mod tests {
179 use super::*;
180
181 pub const SECRET: &str =
182 "{\"installed\":{\"auth_uri\":\"https://accounts.google.com/o/oauth2/auth\",\
183 \"client_secret\":\"UqkDJd5RFwnHoiG5x5Rub8SI\",\"token_uri\":\"https://accounts.google.\
184 com/o/oauth2/token\",\"client_email\":\"\",\"redirect_uris\":[\"urn:ietf:wg:oauth:2.0:\
185 oob\",\"oob\"],\"client_x509_cert_url\":\"\",\"client_id\":\
186 \"14070749909-vgip2f1okm7bkvajhi9jugan6126io9v.apps.googleusercontent.com\",\
187 \"auth_provider_x509_cert_url\":\"https://www.googleapis.com/oauth2/v1/certs\"}}";
188
189 #[test]
190 fn console_secret() {
191 use serde_json as json;
192 match json::from_str::<ConsoleApplicationSecret>(SECRET) {
193 Ok(s) => assert!(s.installed.is_some() && s.web.is_none()),
194 Err(err) => panic!(
195 "Encountered error parsing ConsoleApplicationSecret: {}",
196 err
197 ),
198 }
199 }
200
201 #[test]
202 fn default_expiry_for_id_token_only() {
203 let json = r#"{"id_token": "id"}"#;
205
206 let token = TokenInfo::from_json(json.as_bytes()).unwrap();
207 assert_eq!(token.id_token, Some("id".to_owned()));
208
209 let expiry = token.expires_at.unwrap();
210 assert!(expiry <= time::OffsetDateTime::now_utc() + time::Duration::HOUR);
211 }
212
213 #[test]
214 fn no_default_expiry_for_access_token() {
215 let json = r#"{"access_token": "access", "id_token": "id"}"#;
217
218 let token = TokenInfo::from_json(json.as_bytes()).unwrap();
219 assert_eq!(token.access_token, Some("access".to_owned()));
220 assert_eq!(token.id_token, Some("id".to_owned()));
221 assert_eq!(token.expires_at, None);
222 }
223}