diff --git a/examples/custom_chrono.rs b/examples/custom_chrono.rs index 561ad10..081cad4 100644 --- a/examples/custom_chrono.rs +++ b/examples/custom_chrono.rs @@ -13,6 +13,19 @@ struct Claims { exp: DateTime, } +impl Claims { + /// If a token should always be equal to its representation after serializing and deserializing + /// again, this function must be used for construction. `DateTime` contains a microsecond field + /// but JWT timestamps are defined as UNIX timestamps (seconds). This function normalizes the + /// timestamps. + pub fn new(sub: String, iat: DateTime, exp: DateTime) -> Self { + // normalize the timestamps by stripping of microseconds + let iat = iat.date().and_hms_milli(iat.hour(), iat.minute(), iat.second(), 0); + let exp = exp.date().and_hms_milli(exp.hour(), exp.minute(), exp.second(), 0); + Self { sub, iat, exp } + } +} + mod jwt_numeric_date { //! Custom serialization of DateTime to conform with the JWT spec (RFC 7519 section 2, "Numeric Date") use chrono::{DateTime, TimeZone, Utc}; @@ -42,6 +55,8 @@ mod jwt_numeric_date { const EXPECTED_TOKEN: &str = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJDdXN0b20gRGF0ZVRpbWUgc2VyL2RlIiwiaWF0IjowLCJleHAiOjMyNTAzNjgwMDAwfQ.RTgha0S53MjPC2pMA4e2oMzaBxSY3DMjiYR2qFfV55A"; use super::super::{Claims, SECRET}; + use chrono::{Duration, TimeZone, Utc}; + use jsonwebtoken::{decode, encode, DecodingKey, EncodingKey, Header, Validation}; #[test] fn round_trip() { @@ -49,7 +64,7 @@ mod jwt_numeric_date { let iat = Utc.timestamp(0, 0); let exp = Utc.timestamp(32503680000, 0); - let claims = Claims { sub: sub.clone(), iat, exp }; + let claims = Claims::new(sub.clone(), iat, exp); let token = encode(&Header::default(), &claims, &EncodingKey::from_secret(SECRET.as_ref())) @@ -72,11 +87,37 @@ mod jwt_numeric_date { // A token with the expiry of i64::MAX + 1 let overflow_token = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJDdXN0b20gRGF0ZVRpbWUgc2VyL2RlIiwiaWF0IjowLCJleHAiOjkyMjMzNzIwMzY4NTQ3NzYwMDB9.G2PKreA27U8_xOwuIeCYXacFYeR46f9FyENIZfCrvEc"; - let decode_result = - decode::(&overflow_token, SECRET.as_ref(), &Validation::default()); + let decode_result = decode::( + &overflow_token, + &DecodingKey::from_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_secret(SECRET.as_ref())) + .expect("Failed to encode claims"); + + let decoded = decode::( + &token, + &DecodingKey::from_secret(SECRET.as_ref()), + &Validation::default(), + ) + .expect("Failed to decode token") + .claims; + + assert_eq!(claims, decoded); + } } } @@ -85,7 +126,7 @@ fn main() -> Result<(), Box> { let iat = Utc::now(); let exp = iat + chrono::Duration::days(1); - let claims = Claims { sub: sub.clone(), iat, exp }; + let claims = Claims::new(sub.clone(), iat, exp); let token = jsonwebtoken::encode( &Header::default(),