jsonwebtoken-google-wasm 0.0.8

parse and validate google jwt token compliant with webassembly runtimes with jsonwebtoken
Documentation
/// Based upon [avkviring/jsonwebtoken-google](https://github.com/avkviring/jsonwebtoken-google) which can't target WASM due to a `rand` crate dependency.
use crate::keys::{GooglePublicKeyProvider, KeyProvider, ProviderError};
use serde::de::DeserializeOwned;
use std::time::SystemTime;
use thiserror::Error;

use jwt_simple::prelude::*;

mod keys;
#[cfg(any(test, feature = "test-helper"))]
pub mod test_helper;

pub struct Parser {
    client_id: String,
    key_provider: Box<dyn KeyProvider>,
}

#[derive(Error, Debug)]
pub enum ParserError {
    #[error("Wrong header.")]
    WrongHeader,
    #[error("Wrong provider.")]
    KeyProvider(ProviderError),
    #[error("Unknown kid.")]
    UnknownKid,
    #[error("No kid.")]
    NoKid,
    #[error("Verification error - {0}.")]
    VerificationError(jwt_simple::Error),
    #[error("Verification panic.")]
    VerificationPanic,
}

impl Parser {
    pub const GOOGLE_CERT_URL: &'static str =
        "https://www.googleapis.com/service_accounts/v1/jwk/securetoken@system.gserviceaccount.com";

    pub fn new(client_id: &str) -> Self {
        Parser::new_with_custom_cert_url(client_id, Parser::GOOGLE_CERT_URL)
    }

    pub fn new_with_custom_cert_url(client_id: &str, public_key_url: &str) -> Self {
        Self {
            client_id: client_id.to_owned(),
            key_provider: Box::new(GooglePublicKeyProvider::new(public_key_url)),
        }
    }

    /// Parses and validates the provided token. Validation is done against the associated DecodingKey for the kid of the token, fetched from the key_provider (google).
    pub async fn parse<T: Serialize + DeserializeOwned>(
        &mut self,
        token: &str,
        now: SystemTime,
    ) -> Result<JWTClaims<T>, ParserError> {
        let metadata = Token::decode_metadata(token);
        let metadata = metadata.map_err(|_| ParserError::WrongHeader)?;
        // get cert from metadata key
        let key_id = metadata.key_id();
        let key_id = key_id.ok_or(ParserError::NoKid)?;

        let key = self.key_provider.as_mut().get_key(key_id, now).await;
        let key = key.map_err(ParserError::KeyProvider)?;

        let mut options = VerificationOptions::default();

        let mut auds: HashSet<String> = HashSet::new();
        auds.insert(self.client_id.clone());
        options.allowed_audiences = Some(auds);

        let mut issuers: HashSet<String> = HashSet::new();
        issuers.insert(format!("https://securetoken.google.com/{}", self.client_id));
        options.allowed_issuers = Some(issuers);
        options.required_key_id = Some(key_id.to_string());

        let claims = std::panic::catch_unwind(|| key.verify_token::<T>(token, Some(options)));
        let claims = claims.map_err(|_| ParserError::VerificationPanic)?;
        let claims = claims.map_err(ParserError::VerificationError)?;

        Ok(claims)
    }
}

#[cfg(test)]
mod tests {
    use crate::test_helper::{setup, TokenClaims};
    use crate::ParserError;
    use std::time::SystemTime;

    #[tokio::test]
    async fn should_email_parsed_correct() {
        let claims = TokenClaims::new();
        let email = claims.custom.email.clone();
        let (token, mut parser, _server) = setup(claims);
        let result = parser
            .parse::<TokenClaims>(token.as_str(), SystemTime::now())
            .await;
        let result = result.unwrap();
        assert_eq!(result.custom.email, email);

        let result = parser
            .parse::<TokenClaims>(token.as_str(), SystemTime::now())
            .await;
        let result = result.unwrap();
        assert_eq!(result.custom.email, email);
    }

    #[tokio::test]
    async fn should_validate_exp() {
        let claims = TokenClaims::new_expired();
        let (token, mut validator, _server) = setup(claims);
        let result = validator
            .parse::<TokenClaims>(token.as_str(), SystemTime::now())
            .await;

        assert!(matches!(
            result.err().unwrap(),
            ParserError::VerificationError(_)
        ));
    }

    #[tokio::test]
    async fn should_validate_iss() {
        let claims = TokenClaims::new_with_iss("https://some.com");

        let (token, mut validator, _server) = setup(claims);
        let result = validator
            .parse::<TokenClaims>(token.as_str(), SystemTime::now())
            .await;

        assert!(matches!(
            result.err().unwrap(),
            ParserError::VerificationError(_)
        ));
    }

    #[tokio::test]
    async fn should_validate_aud() {
        let claims = TokenClaims::new_with_aud("other-id");
        let (token, mut validator, _server) = setup(claims);
        let result = validator
            .parse::<TokenClaims>(token.as_str(), SystemTime::now())
            .await;

        assert!(matches!(
            result.err().unwrap(),
            ParserError::VerificationError(_)
        ));
    }
}