use crate::error::Error;
use openssl::{pkey::PKey, rsa::Rsa};
use serde::{Deserialize, Serialize};
pub struct Token {
token: Option<(String, u64)>,
access_scope: String,
}
#[derive(Serialize)]
struct Claims {
iss: String,
scope: String,
aud: String,
exp: u64,
iat: u64,
}
#[derive(Deserialize, Debug)]
struct TokenResponse {
access_token: String,
expires_in: usize,
token_type: String,
}
impl Token {
pub fn new(scope: &str) -> Self {
Self {
token: None,
access_scope: scope.to_string(),
}
}
pub fn get<'a>(&'a mut self) -> Result<String, Error> {
match self.token {
Some((ref token, exp)) if exp > now() => Ok(token.clone()),
_ => self.retrieve(),
}
}
fn retrieve(&mut self) -> Result<String, Error> {
self.token = Some(Self::get_token(&self.access_scope)?);
match self.token {
Some(ref token) => Ok(token.0.clone()),
None => unreachable!(),
}
}
fn get_token(scope: &str) -> Result<(String, u64), Error> {
let now = now();
let exp = now + 3600;
let claims = Claims {
iss: crate::SERVICE_ACCOUNT.client_email.clone(),
scope: scope.into(),
aud: "https://www.googleapis.com/oauth2/v4/token".to_string(),
exp,
iat: now,
};
let mut header = jsonwebtoken::Header::default();
header.alg = jsonwebtoken::Algorithm::RS256;
let rsa = Rsa::private_key_from_pem(crate::SERVICE_ACCOUNT.private_key.as_bytes())?;
let private_key = PKey::from_rsa(rsa)?;
let private_key = &private_key.private_key_to_der().unwrap();
let jwt = jsonwebtoken::encode(&header, &claims, private_key)?;
let body = [
("grant_type", "urn:ietf:params:oauth:grant-type:jwt-bearer"),
("assertion", &jwt),
];
let client = reqwest::blocking::Client::new();
let response: TokenResponse = client
.post("https://www.googleapis.com/oauth2/v4/token")
.form(&body)
.send()?
.json()?;
Ok((response.access_token, exp))
}
}
fn now() -> u64 {
std::time::SystemTime::now()
.duration_since(std::time::SystemTime::UNIX_EPOCH)
.unwrap()
.as_secs()
}