jsonwebtoken_google/
lib.rs

1extern crate core;
2
3use jsonwebtoken::{Algorithm, Validation};
4use serde::de::DeserializeOwned;
5use thiserror::Error;
6
7use crate::keys::{GoogleKeyProviderError, GooglePublicKeyProvider};
8
9mod keys;
10
11#[cfg(any(test, feature = "test-helper"))]
12pub mod test_helper;
13
14
15///
16/// Parser errors
17///
18#[derive(Error, Debug)]
19pub enum ParserError {
20    #[error("Wrong header.")]
21    WrongHeader,
22    #[error("Unknown kid.")]
23    UnknownKid,
24    #[error("Download public key error - {0}.")]
25    KeyProvider(GoogleKeyProviderError),
26    #[error("Wrong token format - {0}.")]
27    WrongToken(jsonwebtoken::errors::Error),
28}
29
30///
31/// Parse & Validate Google JWT token.
32/// Use public key from http(s) server.
33///
34pub struct Parser {
35    client_id: String,
36    key_provider: tokio::sync::Mutex<GooglePublicKeyProvider>,
37}
38
39impl Parser {
40    pub const GOOGLE_CERT_URL: &'static str = "https://www.googleapis.com/oauth2/v3/certs";
41
42    pub fn new(client_id: &str) -> Self {
43        Parser::new_with_custom_cert_url(client_id, Parser::GOOGLE_CERT_URL)
44    }
45
46    pub fn new_with_custom_cert_url(client_id: &str, public_key_url: &str) -> Self {
47        Self {
48            client_id: client_id.to_owned(),
49            key_provider: tokio::sync::Mutex::new(GooglePublicKeyProvider::new(public_key_url)),
50        }
51    }
52
53    ///
54    /// Parse and validate token.
55    /// Download and cache public keys from http(s) server.
56    /// Use expire time header for reload keys.
57    ///
58    pub async fn parse<T: DeserializeOwned>(&self, token: &str) -> Result<T, ParserError> {
59        let mut provider = self.key_provider.lock().await;
60        match jsonwebtoken::decode_header(token) {
61            Ok(header) => match header.kid {
62                None => Result::Err(ParserError::UnknownKid),
63                Some(kid) => match provider.get_key(kid.as_str()).await {
64                    Ok(key) => {
65                        let aud = vec![self.client_id.to_owned()];
66                        let mut validation = Validation::new(Algorithm::RS256);
67                        validation.set_audience(&aud);
68                        validation.set_issuer(&["https://accounts.google.com".to_string(), "accounts.google.com".to_string()]);
69                        validation.validate_exp = true;
70                        validation.validate_nbf = false;
71                        let result = jsonwebtoken::decode::<T>(token, &key, &validation);
72                        match result {
73                            Result::Ok(token_data) => Result::Ok(token_data.claims),
74                            Result::Err(error) => Result::Err(ParserError::WrongToken(error)),
75                        }
76                    }
77                    Err(e) => {
78                        let error = ParserError::KeyProvider(e);
79                        Result::Err(error)
80                    }
81                },
82            },
83            Err(_) => Result::Err(ParserError::WrongHeader),
84        }
85    }
86}
87
88#[cfg(test)]
89mod tests {
90    use jsonwebtoken::errors::ErrorKind;
91
92    use crate::ParserError;
93    use crate::test_helper::{setup, TokenClaims};
94
95    #[tokio::test]
96    async fn should_correct() {
97        let claims = TokenClaims::new();
98        let (token, parser, _server) = setup(&claims);
99        let result = parser.parse::<TokenClaims>(token.as_str()).await;
100        let result = result.unwrap();
101        assert_eq!(result.email, claims.email);
102    }
103
104    #[tokio::test]
105    async fn should_validate_exp() {
106        let claims = TokenClaims::new_expired();
107        let (token, validator, _server) = setup(&claims);
108        let result = validator.parse::<TokenClaims>(token.as_str()).await;
109
110        assert!(
111            if let ParserError::WrongToken(error) = result.err().unwrap() {
112                if let ErrorKind::ExpiredSignature = error.into_kind() {
113                    true
114                } else {
115                    false
116                }
117            } else {
118                false
119            }
120        );
121    }
122
123    #[tokio::test]
124    async fn should_validate_iss() {
125        let mut claims = TokenClaims::new();
126        claims.iss = "https://some.com".to_owned();
127        let (token, validator, _server) = setup(&claims);
128        let result = validator.parse::<TokenClaims>(token.as_str()).await;
129        assert!(
130            if let ParserError::WrongToken(error) = result.err().unwrap() {
131                if let ErrorKind::InvalidIssuer = error.into_kind() {
132                    true
133                } else {
134                    false
135                }
136            } else {
137                false
138            }
139        );
140    }
141
142    #[tokio::test]
143    async fn should_validate_aud() {
144        let mut claims = TokenClaims::new();
145        claims.aud = "other-id".to_owned();
146        let (token, validator, _server) = setup(&claims);
147        let result = validator.parse::<TokenClaims>(token.as_str()).await;
148        assert!(
149            if let ParserError::WrongToken(error) = result.err().unwrap() {
150                if let ErrorKind::InvalidAudience = error.into_kind() {
151                    true
152                } else {
153                    false
154                }
155            } else {
156                false
157            }
158        );
159    }
160}