jsonwebtoken_google_wasm/
lib.rs1use 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 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 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}