jsonwebtoken-google-wasm 0.0.8

parse and validate google jwt token compliant with webassembly runtimes with jsonwebtoken
Documentation
use base64::{engine::general_purpose::URL_SAFE_NO_PAD, Engine};
use httpmock::MockServer;

use jwt_simple::prelude::*;
use rand::thread_rng;
use rsa::pkcs8::EncodePrivateKey;
use rsa::{traits::PublicKeyParts, RsaPrivateKey};
use serde::{Deserialize, Serialize};

use crate::Parser;

pub const KID: &str = "some-kid";
pub const CLIENT_ID: &str = "some-client-id";
pub const EMAIL: &str = "alex@kviring.com";
pub const SUB: &str = "11112222333344445555";

#[derive(Debug, Serialize, Deserialize)]
pub struct TokenClaims {
    pub email: String,
}

impl TokenClaims {
    pub fn new() -> JWTClaims<TokenClaims> {
        let claims = TokenClaims {
            email: EMAIL.to_string(),
        };
        Claims::with_custom_claims(claims, Duration::from_hours(2))
            .with_audience(CLIENT_ID.to_string())
            .with_issuer(format!("https://securetoken.google.com/{}", CLIENT_ID))
            .with_subject(SUB.to_string())
    }

    pub fn new_expired() -> JWTClaims<TokenClaims> {
        let mut claim = TokenClaims::new();
        claim.expires_at = Some(Duration::from_secs(0));
        claim
    }

    pub fn new_with_iss(iss: &str) -> JWTClaims<TokenClaims> {
        TokenClaims::new().with_issuer(iss)
    }

    pub fn new_with_aud(aud: &str) -> JWTClaims<TokenClaims> {
        TokenClaims::new().with_audience(aud)
    }
}

impl Default for TokenClaims {
    fn default() -> Self {
        TokenClaims {
            email: "".to_string(),
        }
    }
}

pub fn setup(claims: JWTClaims<TokenClaims>) -> (String, Parser, MockServer) {
    let (token, server) = setup_public_key_server(claims);
    (
        token,
        Parser::new_with_custom_cert_url(CLIENT_ID, server.url("/").as_str()),
        server,
    )
}

pub fn setup_public_key_server(claims: JWTClaims<TokenClaims>) -> (String, MockServer) {
    let bits = 2048;
    let private_key =
        RsaPrivateKey::new(&mut thread_rng(), bits).expect("failed to generate a key");
    let pem = private_key
        .to_pkcs8_pem(rsa::pkcs8::LineEnding::LF)
        .unwrap();
    let key = RS256KeyPair::from_pem(pem.as_str())
        .unwrap()
        .with_key_id(KID);

    let token = key.sign(claims).unwrap();

    let n = URL_SAFE_NO_PAD.encode(private_key.n().to_bytes_be());
    let e = URL_SAFE_NO_PAD.encode(private_key.e().to_bytes_be());
    let resp = format!("{{\"keys\": [{{\"kty\": \"RSA\",\"use\": \"sig\",\"e\": \"{}\",\"n\": \"{}\",\"alg\": \"RS256\",\"kid\": \"{}\"}}]}}", e, n, KID);

    let server = MockServer::start();
    server.mock(|when, then| {
        when.method(httpmock::Method::GET).path("/");

        then.status(200)
            .header(
                "cache-control",
                "public, max-age=24920, must-revalidate, no-transform",
            )
            .header("Content-Type", "application/json; charset=UTF-8")
            .body(resp);
    });
    (token, server)
}