use chrono::prelude::*;
use jsonwebtoken_rustcrypto::{DecodingKey, EncodingKey, Header, Validation};
use serde::{Deserialize, Serialize};
const SECRET: &str = "some-secret";
#[derive(Debug, PartialEq, Serialize, Deserialize)]
struct Claims {
sub: String,
#[serde(with = "jwt_numeric_date")]
iat: DateTime<Utc>,
#[serde(with = "jwt_numeric_date")]
exp: DateTime<Utc>,
}
impl Claims {
pub fn new(sub: String, iat: DateTime<Utc>, exp: DateTime<Utc>) -> Self {
let iat = iat.with_nanosecond(0).unwrap();
let exp = exp.with_nanosecond(0).unwrap();
Self { sub, iat, exp }
}
}
mod jwt_numeric_date {
use chrono::{DateTime, TimeZone, Utc};
use serde::{self, Deserialize, Deserializer, Serializer};
pub fn serialize<S>(date: &DateTime<Utc>, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let timestamp = date.timestamp();
serializer.serialize_i64(timestamp)
}
pub fn deserialize<'de, D>(deserializer: D) -> Result<DateTime<Utc>, D::Error>
where
D: Deserializer<'de>,
{
Utc.timestamp_opt(i64::deserialize(deserializer)?, 0)
.single() .ok_or_else(|| serde::de::Error::custom("invalid Unix timestamp value"))
}
#[cfg(test)]
mod tests {
const EXPECTED_TOKEN: &str = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJDdXN0b20gRGF0ZVRpbWUgc2VyL2RlIiwiaWF0IjowLCJleHAiOjMyNTAzNjgwMDAwfQ.RTgha0S53MjPC2pMA4e2oMzaBxSY3DMjiYR2qFfV55A";
use super::super::{Claims, SECRET};
use chrono::{Duration, TimeZone, Utc};
use jsonwebtoken_rustcrypto::{
decode, encode, DecodingKey, EncodingKey, Header, Validation,
};
#[test]
fn round_trip() {
let sub = "Custom DateTime ser/de".to_string();
let iat = Utc.timestamp(0, 0);
let exp = Utc.timestamp(32503680000, 0);
let claims = Claims::new(sub.clone(), iat, exp);
let token = encode(
&Header::default(),
&claims,
&EncodingKey::from_hmac_secret(SECRET.as_ref()),
)
.expect("Failed to encode claims");
assert_eq!(&token, EXPECTED_TOKEN);
let decoded = decode::<Claims>(
&token,
&DecodingKey::from_hmac_secret(SECRET.as_ref()),
&Validation::default(),
)
.expect("Failed to decode token");
assert_eq!(decoded.claims, claims);
}
#[test]
fn should_fail_on_invalid_timestamp() {
let overflow_token = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJDdXN0b20gRGF0ZVRpbWUgc2VyL2RlIiwiaWF0IjowLCJleHAiOjkyMjMzNzIwMzY4NTQ3NzYwMDB9.G2PKreA27U8_xOwuIeCYXacFYeR46f9FyENIZfCrvEc";
let decode_result = decode::<Claims>(
&overflow_token,
&DecodingKey::from_hmac_secret(SECRET.as_ref()),
&Validation::default(),
);
assert!(decode_result.is_err());
}
#[test]
fn to_token_and_parse_equals_identity() {
let iat = Utc::now();
let exp = iat + Duration::days(1);
let sub = "Custom DateTime ser/de".to_string();
let claims = Claims::new(sub.clone(), iat, exp);
let token = encode(
&Header::default(),
&claims,
&EncodingKey::from_hmac_secret(SECRET.as_ref()),
)
.expect("Failed to encode claims");
let decoded = decode::<Claims>(
&token,
&DecodingKey::from_hmac_secret(SECRET.as_ref()),
&Validation::default(),
)
.expect("Failed to decode token")
.claims;
assert_eq!(claims, decoded);
}
}
}
fn main() -> Result<(), Box<dyn std::error::Error>> {
let sub = "Custom DateTime ser/de".to_string();
let iat = Utc::now();
let exp = iat + chrono::Duration::days(1);
let claims = Claims::new(sub, iat, exp);
let token = jsonwebtoken_rustcrypto::encode(
&Header::default(),
&claims,
&EncodingKey::from_hmac_secret(SECRET.as_ref()),
)?;
println!("serialized token: {}", &token);
let token_data = jsonwebtoken_rustcrypto::decode::<Claims>(
&token,
&DecodingKey::from_hmac_secret(SECRET.as_ref()),
&Validation::default(),
)?;
println!("token data:\n{:#?}", &token_data);
Ok(())
}