use crate::keys::{GooglePublicKeyProvider, KeyProvider, ProviderError};
use jsonwebtoken::{Algorithm, Validation};
use serde::de::DeserializeOwned;
use thiserror::Error;
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("Wrong token format - {0}.")]
WrongToken(jsonwebtoken::errors::Error),
}
impl Parser {
pub const GOOGLE_CERT_URL: &'static str = "https://www.googleapis.com/oauth2/v3/certs";
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: DeserializeOwned>(&mut self, token: &str) -> Result<T, ParserError> {
let header = jsonwebtoken::decode_header(token).map_err(|_| ParserError::WrongHeader)?;
let kid = header.kid.ok_or(ParserError::UnknownKid)?;
let key = self
.key_provider
.as_mut()
.get_key(kid.as_str())
.await
.map_err(ParserError::KeyProvider)?;
let aud = vec![self.client_id.to_owned()];
let mut validation = Validation::new(Algorithm::RS256);
validation.set_audience(&aud);
validation.set_issuer(&[
"https://accounts.google.com".to_string(),
"accounts.google.com".to_string(),
]);
validation.validate_exp = true;
validation.validate_nbf = false;
let data =
jsonwebtoken::decode::<T>(token, &key, &validation).map_err(ParserError::WrongToken)?;
Ok(data.claims)
}
}
#[cfg(test)]
mod tests {
use jsonwebtoken::errors::ErrorKind;
use crate::test_helper::{setup, TokenClaims};
use crate::ParserError;
#[tokio::test]
async fn should_email_parsed_correct() {
let claims = TokenClaims::new();
let (token, mut parser, _server) = setup(&claims);
let result = parser.parse::<TokenClaims>(token.as_str()).await;
let result = result.unwrap();
assert_eq!(result.email, claims.email);
let result = parser.parse::<TokenClaims>(token.as_str()).await;
let result = result.unwrap();
assert_eq!(result.email, claims.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()).await;
assert!(
if let ParserError::WrongToken(error) = result.err().unwrap() {
matches!(error.into_kind(), ErrorKind::ExpiredSignature)
} else {
false
}
);
}
#[tokio::test]
async fn should_validate_iss() {
let mut claims = TokenClaims::new();
claims.iss = "https://some.com".to_owned();
let (token, mut validator, _server) = setup(&claims);
let result = validator.parse::<TokenClaims>(token.as_str()).await;
assert!(
if let ParserError::WrongToken(error) = result.err().unwrap() {
matches!(error.into_kind(), ErrorKind::InvalidIssuer)
} else {
false
}
);
}
#[tokio::test]
async fn should_validate_aud() {
let mut claims = TokenClaims::new();
claims.aud = "other-id".to_owned();
let (token, mut validator, _server) = setup(&claims);
let result = validator.parse::<TokenClaims>(token.as_str()).await;
assert!(
if let ParserError::WrongToken(error) = result.err().unwrap() {
matches!(error.into_kind(), ErrorKind::InvalidAudience)
} else {
false
}
);
}
}