use crate::error::{AuthErrorOr, Error};
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Deserialize, Serialize)]
pub struct AccessToken {
value: String,
expires_at: Option<DateTime<Utc>>,
}
impl AccessToken {
pub fn as_str(&self) -> &str {
&self.value
}
pub fn expiration_time(&self) -> Option<DateTime<Utc>> {
self.expires_at
}
pub fn is_expired(&self) -> bool {
self.expires_at
.map(|expiration_time| expiration_time - chrono::Duration::minutes(1) <= Utc::now())
.unwrap_or(false)
}
}
impl AsRef<str> for AccessToken {
fn as_ref(&self) -> &str {
self.as_str()
}
}
impl From<TokenInfo> for AccessToken {
fn from(value: TokenInfo) -> Self {
AccessToken {
value: value.access_token,
expires_at: value.expires_at,
}
}
}
#[derive(Clone, PartialEq, Debug, Deserialize, Serialize)]
pub(crate) struct TokenInfo {
pub(crate) access_token: String,
pub(crate) refresh_token: Option<String>,
pub(crate) expires_at: Option<DateTime<Utc>>,
}
impl TokenInfo {
pub(crate) fn from_json(json_data: &[u8]) -> Result<TokenInfo, Error> {
#[derive(Deserialize)]
struct RawToken {
access_token: String,
refresh_token: Option<String>,
token_type: String,
expires_in: Option<i64>,
}
let RawToken {
access_token,
refresh_token,
token_type,
expires_in,
} = serde_json::from_slice::<AuthErrorOr<RawToken>>(json_data)?.into_result()?;
if token_type.to_lowercase().as_str() != "bearer" {
use std::io;
return Err(io::Error::new(
io::ErrorKind::InvalidData,
format!(
r#"unknown token type returned; expected "bearer" found {}"#,
token_type
),
)
.into());
}
let expires_at = expires_in
.map(|seconds_from_now| Utc::now() + chrono::Duration::seconds(seconds_from_now));
Ok(TokenInfo {
access_token,
refresh_token,
expires_at,
})
}
pub fn is_expired(&self) -> bool {
self.expires_at
.map(|expiration_time| expiration_time - chrono::Duration::minutes(1) <= Utc::now())
.unwrap_or(false)
}
}
#[derive(Deserialize, Serialize, Clone, Default, Debug)]
pub struct ApplicationSecret {
pub client_id: String,
pub client_secret: String,
pub token_uri: String,
pub auth_uri: String,
pub redirect_uris: Vec<String>,
pub project_id: Option<String>,
pub client_email: Option<String>,
pub auth_provider_x509_cert_url: Option<String>,
pub client_x509_cert_url: Option<String>,
}
#[derive(Deserialize, Serialize, Default, Debug)]
pub struct ConsoleApplicationSecret {
pub web: Option<ApplicationSecret>,
pub installed: Option<ApplicationSecret>,
}
#[cfg(test)]
pub mod tests {
use super::*;
pub const SECRET: &'static str =
"{\"installed\":{\"auth_uri\":\"https://accounts.google.com/o/oauth2/auth\",\
\"client_secret\":\"UqkDJd5RFwnHoiG5x5Rub8SI\",\"token_uri\":\"https://accounts.google.\
com/o/oauth2/token\",\"client_email\":\"\",\"redirect_uris\":[\"urn:ietf:wg:oauth:2.0:\
oob\",\"oob\"],\"client_x509_cert_url\":\"\",\"client_id\":\
\"14070749909-vgip2f1okm7bkvajhi9jugan6126io9v.apps.googleusercontent.com\",\
\"auth_provider_x509_cert_url\":\"https://www.googleapis.com/oauth2/v1/certs\"}}";
#[test]
fn console_secret() {
use serde_json as json;
match json::from_str::<ConsoleApplicationSecret>(SECRET) {
Ok(s) => assert!(s.installed.is_some() && s.web.is_none()),
Err(err) => panic!(
"Encountered error parsing ConsoleApplicationSecret: {}",
err
),
}
}
}