use super::Codec;
use crate::errors::JwtError;
use crate::errors::JwtOperation;
use crate::errors::{Error, Result};
use crate::jsonwebtoken::{DecodingKey, EncodingKey, Header, Validation};
pub use validation_result::JwtValidationResult;
pub use validation_service::JwtValidationService;
use std::collections::HashSet;
use std::marker::PhantomData;
use chrono::Utc;
use serde::{Deserialize, Serialize, de::DeserializeOwned};
use serde_with::skip_serializing_none;
mod validation_result;
mod validation_service;
#[derive(Serialize, Deserialize, Clone, Debug)]
#[skip_serializing_none]
pub struct RegisteredClaims {
#[serde(rename = "iss")]
pub issuer: String,
#[serde(rename = "sub")]
pub subject: Option<String>,
#[serde(rename = "aud")]
pub audience: Option<HashSet<String>>,
#[serde(rename = "exp")]
pub expiration_time: u64,
#[serde(rename = "nbf")]
pub not_before_time: Option<u64>,
#[serde(rename = "iat")]
pub issued_at_time: u64,
#[serde(rename = "jti")]
pub jwt_id: Option<String>,
}
impl RegisteredClaims {
pub fn new(issuer: &str, expiration_time: u64) -> Self {
Self {
issuer: issuer.to_string(),
subject: None,
audience: None,
expiration_time,
not_before_time: None,
issued_at_time: Utc::now().timestamp() as u64,
jwt_id: None,
}
}
}
#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct JwtClaims<CustomClaims> {
#[serde(flatten)]
pub registered_claims: RegisteredClaims,
#[serde(flatten)]
pub custom_claims: CustomClaims,
}
impl<CustomClaims> JwtClaims<CustomClaims> {
pub fn new(custom_claims: CustomClaims, registered_claims: RegisteredClaims) -> Self {
Self {
custom_claims,
registered_claims,
}
}
pub fn has_issuer(&self, issuer: &str) -> bool {
self.registered_claims.issuer == issuer
}
}
pub struct JsonWebTokenOptions {
pub enc_key: EncodingKey,
pub dec_key: DecodingKey,
pub header: Option<Header>,
pub validation: Option<Validation>,
}
impl Default for JsonWebTokenOptions {
fn default() -> Self {
use rand::{Rng, distr::Alphanumeric, rng};
let authentication_secret: String = rng()
.sample_iter(&Alphanumeric)
.take(60)
.map(char::from)
.collect();
Self {
enc_key: EncodingKey::from_secret(authentication_secret.as_bytes()),
dec_key: DecodingKey::from_secret(authentication_secret.as_bytes()),
header: Some(Header::default()),
validation: Some(Validation::default()),
}
}
}
impl JsonWebTokenOptions {
pub fn with_encoding_key(self, enc_key: EncodingKey) -> Self {
Self { enc_key, ..self }
}
pub fn with_decoding_key(self, dec_key: DecodingKey) -> Self {
Self { dec_key, ..self }
}
pub fn with_header(self, header: Header) -> Self {
Self {
header: Some(header),
..self
}
}
pub fn with_validation(self, validation: Validation) -> Self {
Self {
validation: Some(validation),
..self
}
}
}
#[derive(Clone)]
pub struct JsonWebToken<P> {
enc_key: EncodingKey,
dec_key: DecodingKey,
pub header: Header,
pub validation: Validation,
phantom_payload: PhantomData<P>,
}
impl<P> JsonWebToken<P> {
pub fn new_with_options(options: JsonWebTokenOptions) -> Self {
let JsonWebTokenOptions {
enc_key,
dec_key,
header,
validation,
} = options;
Self {
enc_key,
dec_key,
header: header.unwrap_or(Header::default()),
validation: validation.unwrap_or(Validation::default()),
phantom_payload: PhantomData,
}
}
}
impl<P> Default for JsonWebToken<P> {
fn default() -> Self {
Self::new_with_options(JsonWebTokenOptions::default())
}
}
impl<P> Codec for JsonWebToken<P>
where
P: Serialize + DeserializeOwned + Clone,
{
type Payload = P;
fn encode(&self, payload: &Self::Payload) -> Result<Vec<u8>> {
let web_token =
jsonwebtoken::encode(&self.header, payload, &self.enc_key).map_err(|e| {
Error::Jwt(JwtError::processing(
JwtOperation::Encode,
format!("JWT encoding failed: {e}"),
))
})?;
Ok(web_token.as_bytes().to_vec())
}
fn decode(&self, encoded_value: &[u8]) -> Result<Self::Payload> {
let claims =
jsonwebtoken::decode::<Self::Payload>(&encoded_value, &self.dec_key, &self.validation)
.map_err(|e| {
Error::Jwt(JwtError::processing_with_preview(
JwtOperation::Decode,
format!("JWT decoding failed: {e}"),
Some(
String::from_utf8_lossy(encoded_value)
.chars()
.take(20)
.collect::<String>()
+ "...",
),
))
})?;
if self.header != claims.header {
return Err(Error::Jwt(JwtError::processing(
JwtOperation::Validate,
"Header of the decoded value does not match the one used for encoding".to_string(),
)));
}
Ok(claims.claims)
}
}