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)),
}
}
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)?;
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(_)
));
}
}