use crate::jwt::{
claims::basic::JwtBasicClaims, error::JwtError, header::JwtHeader, JWT_DELIMITER,
JWT_VALIDATION_TIME_LEEWAY_SECS,
};
use chrono::Utc;
use ethers::core::k256::ecdsa::SigningKey;
use serde::{de::DeserializeOwned, Serialize};
use std::collections::HashSet;
pub trait VerifiableClaims: Serialize + DeserializeOwned {
fn basic(&self) -> &JwtBasicClaims;
fn encode(&self, key: &SigningKey) -> Result<String, JwtError> {
let encoder = &data_encoding::BASE64URL_NOPAD;
let header = encoder.encode(serde_json::to_string(&JwtHeader::default())?.as_bytes());
let claims = encoder.encode(serde_json::to_string(self)?.as_bytes());
let message = format!("{header}.{claims}");
let signature = encoder.encode(&key.sign_recoverable(message.as_bytes())?.0.to_bytes());
Ok(format!("{message}.{signature}"))
}
fn try_from_str(data: &str) -> Result<Self, JwtError>
where
Self: Sized,
{
let mut parts = data.splitn(3, JWT_DELIMITER);
let (Some(header), Some(claims)) = (parts.next(), parts.next()) else {
return Err(JwtError::Format);
};
let decoder = &data_encoding::BASE64URL_NOPAD;
let header_len = decoder.decode_len(header.len()).map_err(|_| JwtError::Encoding)?;
let claims_len = decoder.decode_len(claims.len()).map_err(|_| JwtError::Encoding)?;
let mut output = vec![0u8; header_len.max(claims_len)];
data_encoding::BASE64URL_NOPAD
.decode_mut(header.as_bytes(), &mut output[..header_len])
.map_err(|_| JwtError::Encoding)?;
{
let header = serde_json::from_slice::<JwtHeader>(&output[..header_len])
.map_err(JwtError::Serialization)?;
if !header.is_valid() {
return Err(JwtError::Header);
}
}
data_encoding::BASE64URL_NOPAD
.decode_mut(claims.as_bytes(), &mut output[..claims_len])
.map_err(|_| JwtError::Encoding)?;
let claims = serde_json::from_slice::<Self>(&output[..claims_len])
.map_err(JwtError::Serialization)?;
let mut parts = data.rsplitn(2, JWT_DELIMITER);
let (Some(signature), Some(message)) = (parts.next(), parts.next()) else {
return Err(JwtError::Format);
};
let key = jsonwebtoken::DecodingKey::from_ed_der(claims.basic().iss.as_ref());
let sig_result = jsonwebtoken::crypto::verify(
signature,
message.as_bytes(),
&key,
jsonwebtoken::Algorithm::EdDSA,
);
match sig_result {
Ok(true) => Ok(claims),
_ => Err(JwtError::Signature),
}
}
fn verify_basic(
&self,
aud: &HashSet<String>,
time_leeway: impl Into<Option<i64>>,
) -> Result<(), JwtError> {
let basic = self.basic();
let time_leeway = time_leeway.into().unwrap_or(JWT_VALIDATION_TIME_LEEWAY_SECS);
let now = Utc::now().timestamp();
if matches!(basic.exp, Some(exp) if now - time_leeway > exp) {
return Err(JwtError::Expired { expiration: basic.exp });
}
if now + time_leeway < basic.iat {
return Err(JwtError::NotYetValid {
basic_iat: basic.iat,
now_time_leeway: now + time_leeway,
time_leeway,
});
}
if !aud.contains(&basic.aud) {
return Err(JwtError::InvalidAudience);
}
Ok(())
}
}