jsonwebtoken_google_wasm/
lib.rs

1/// Based upon [avkviring/jsonwebtoken-google](https://github.com/avkviring/jsonwebtoken-google) which can't target WASM due to a `rand` crate dependency.
2use crate::keys::{GooglePublicKeyProvider, KeyProvider, ProviderError};
3use serde::de::DeserializeOwned;
4use std::time::SystemTime;
5use thiserror::Error;
6
7use jwt_simple::prelude::*;
8
9mod keys;
10#[cfg(any(test, feature = "test-helper"))]
11pub mod test_helper;
12
13pub struct Parser {
14    client_id: String,
15    key_provider: Box<dyn KeyProvider>,
16}
17
18#[derive(Error, Debug)]
19pub enum ParserError {
20    #[error("Wrong header.")]
21    WrongHeader,
22    #[error("Wrong provider.")]
23    KeyProvider(ProviderError),
24    #[error("Unknown kid.")]
25    UnknownKid,
26    #[error("No kid.")]
27    NoKid,
28    #[error("Verification error - {0}.")]
29    VerificationError(jwt_simple::Error),
30    #[error("Verification panic.")]
31    VerificationPanic,
32}
33
34impl Parser {
35    pub const GOOGLE_CERT_URL: &'static str =
36        "https://www.googleapis.com/service_accounts/v1/jwk/securetoken@system.gserviceaccount.com";
37
38    pub fn new(client_id: &str) -> Self {
39        Parser::new_with_custom_cert_url(client_id, Parser::GOOGLE_CERT_URL)
40    }
41
42    pub fn new_with_custom_cert_url(client_id: &str, public_key_url: &str) -> Self {
43        Self {
44            client_id: client_id.to_owned(),
45            key_provider: Box::new(GooglePublicKeyProvider::new(public_key_url)),
46        }
47    }
48
49    /// 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).
50    pub async fn parse<T: Serialize + DeserializeOwned>(
51        &mut self,
52        token: &str,
53        now: SystemTime,
54    ) -> Result<JWTClaims<T>, ParserError> {
55        let metadata = Token::decode_metadata(token);
56        let metadata = metadata.map_err(|_| ParserError::WrongHeader)?;
57        // get cert from metadata key
58        let key_id = metadata.key_id();
59        let key_id = key_id.ok_or(ParserError::NoKid)?;
60
61        let key = self.key_provider.as_mut().get_key(key_id, now).await;
62        let key = key.map_err(ParserError::KeyProvider)?;
63
64        let mut options = VerificationOptions::default();
65
66        let mut auds: HashSet<String> = HashSet::new();
67        auds.insert(self.client_id.clone());
68        options.allowed_audiences = Some(auds);
69
70        let mut issuers: HashSet<String> = HashSet::new();
71        issuers.insert(format!("https://securetoken.google.com/{}", self.client_id));
72        options.allowed_issuers = Some(issuers);
73        options.required_key_id = Some(key_id.to_string());
74
75        let claims = std::panic::catch_unwind(|| key.verify_token::<T>(token, Some(options)));
76        let claims = claims.map_err(|_| ParserError::VerificationPanic)?;
77        let claims = claims.map_err(ParserError::VerificationError)?;
78
79        Ok(claims)
80    }
81}
82
83#[cfg(test)]
84mod tests {
85    use crate::test_helper::{setup, TokenClaims};
86    use crate::ParserError;
87    use std::time::SystemTime;
88
89    #[tokio::test]
90    async fn should_email_parsed_correct() {
91        let claims = TokenClaims::new();
92        let email = claims.custom.email.clone();
93        let (token, mut parser, _server) = setup(claims);
94        let result = parser
95            .parse::<TokenClaims>(token.as_str(), SystemTime::now())
96            .await;
97        let result = result.unwrap();
98        assert_eq!(result.custom.email, email);
99
100        let result = parser
101            .parse::<TokenClaims>(token.as_str(), SystemTime::now())
102            .await;
103        let result = result.unwrap();
104        assert_eq!(result.custom.email, email);
105    }
106
107    #[tokio::test]
108    async fn should_validate_exp() {
109        let claims = TokenClaims::new_expired();
110        let (token, mut validator, _server) = setup(claims);
111        let result = validator
112            .parse::<TokenClaims>(token.as_str(), SystemTime::now())
113            .await;
114
115        assert!(matches!(
116            result.err().unwrap(),
117            ParserError::VerificationError(_)
118        ));
119    }
120
121    #[tokio::test]
122    async fn should_validate_iss() {
123        let claims = TokenClaims::new_with_iss("https://some.com");
124
125        let (token, mut validator, _server) = setup(claims);
126        let result = validator
127            .parse::<TokenClaims>(token.as_str(), SystemTime::now())
128            .await;
129
130        assert!(matches!(
131            result.err().unwrap(),
132            ParserError::VerificationError(_)
133        ));
134    }
135
136    #[tokio::test]
137    async fn should_validate_aud() {
138        let claims = TokenClaims::new_with_aud("other-id");
139        let (token, mut validator, _server) = setup(claims);
140        let result = validator
141            .parse::<TokenClaims>(token.as_str(), SystemTime::now())
142            .await;
143
144        assert!(matches!(
145            result.err().unwrap(),
146            ParserError::VerificationError(_)
147        ));
148    }
149}