google-oauth 1.11.4

Google oauth server-side client
Documentation
#![allow(non_upper_case_globals)]

use std::ops::Add;
use std::sync::Arc;
use web_time::Instant;
use std::time::Duration;
use async_lock::RwLock;
use wasm_bindgen::prelude::*;
use crate::{Cert, Certs, GOOGLE_OAUTH_V3_USER_INFO_API, GOOGLE_SA_CERTS_URL, GoogleAccessTokenPayload, GooglePayload, utils};
use anyhow::bail;
use lazy_static::lazy_static;
use crate::jwt_parser::JwtParser;
use crate::validate::id_token;

lazy_static! {
    static ref ca: reqwest::Client = reqwest::Client::new();
}

#[derive(Debug)]
#[wasm_bindgen(getter_with_clone)]
pub struct Client {
    client_id: String,
    cached_certs: Arc<RwLock<Certs>>
}

#[wasm_bindgen]
impl Client {
    #[wasm_bindgen(constructor)]
    pub fn new(client_id: String) -> Client {
        Client {
            client_id,
            cached_certs: Arc::default(),
        }
    }

    #[wasm_bindgen]
    pub async fn validate_id_token(&self, token: String) -> Result<GooglePayload, String> {
        let parser: JwtParser<GooglePayload> = match JwtParser::parse(&token) {
            Ok(jwt) => jwt,
            Err(e) => return Err(format!("{:?}", e)),
        };

        if let Err(e) = id_token::validate_info(&[&self.client_id], &parser) {
            return Err(format!("{:?}", e));
        }

        let cert = match self.get_cert(parser.header.alg.as_str(), parser.header.kid.as_str()).await {
            Ok(cert) => cert,
            Err(e) => return Err(format!("{:?}", e))
        };

        if let Err(e) = id_token::do_validate(&cert, &parser) {
            return Err(format!("{:?}", e));
        }

        Ok(parser.payload)
    }

    async fn get_cert(&self, alg: &str, kid: &str) -> anyhow::Result<Cert> {
        {
            let cached_certs = self.cached_certs.read().await;
            if !cached_certs.need_refresh() {
                return match cached_certs.find_cert(alg, kid) {
                    Ok(cert) => Ok(cert),
                    Err(e) => bail!("{}", e),
                };
            }
        }

        let mut cached_certs = self.cached_certs.write().await;

        let resp = ca.get(GOOGLE_SA_CERTS_URL)
            .send()
            .await?;

        let max_age = utils::parse_max_age_from_async_resp(&resp);
        let text = resp.text().await?;

        *cached_certs = serde_json::from_str(&text)?;
        cached_certs.set_cache_until(
            Instant::now().add(Duration::from_secs(max_age))
        );

        match cached_certs.find_cert(alg, kid) {
            Ok(cert) => Ok(cert),
            Err(e) => bail!("{}", e),
        }
    }

    #[wasm_bindgen]
    pub async fn validate_access_token(&self, token: String) -> Result<GoogleAccessTokenPayload, String> {
        match self.do_validate_access_token(token.as_str()).await {
            Ok(ret) => Ok(ret),
            Err(e) => Err(format!("{:?}", e)),
        }
    }

    async fn do_validate_access_token(&self, token: &str) -> anyhow::Result<GoogleAccessTokenPayload> {
        let url = format!("{}?access_token={}", GOOGLE_OAUTH_V3_USER_INFO_API, token);

        let info = ca.get(url)
            .send()
            .await?
            .text()
            .await?;

        let payload = serde_json::from_str(&info)?;

        Ok(payload)
    }
}