Skip to content

Commit eb1c026

Browse files
feat!: Simpler JWT NumericDate type. (#693)
1 parent 13fa799 commit eb1c026

File tree

3 files changed

+23
-43
lines changed

3 files changed

+23
-43
lines changed

crates/claims/crates/jwt/src/claims/registered.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -376,6 +376,7 @@ registered_claims! {
376376
/// processing. The processing of the `exp` claim requires that the current
377377
/// date/time MUST be before the expiration date/time listed in the `exp`
378378
/// claim.
379+
#[derive(Copy)]
379380
"exp": ExpirationTime(NumericDate),
380381

381382
/// Not Before (`nbf`) claim.
@@ -385,12 +386,14 @@ registered_claims! {
385386
/// be after or equal to the not-before date/time listed in the "nbf" claim.
386387
/// Implementers MAY provide for some small leeway, usually no more than a
387388
/// few minutes, to account for clock skew.
389+
#[derive(Copy)]
388390
"nbf": NotBefore(NumericDate),
389391

390392
/// Issued At (`iat`) claim.
391393
///
392394
/// Time at which the JWT was issued. This claim can be used to determine
393395
/// the age of the JWT.
396+
#[derive(Copy)]
394397
"iat": IssuedAt(NumericDate),
395398

396399
/// JWT ID (`jti`) claim.
@@ -465,7 +468,7 @@ impl NotBefore {
465468

466469
impl IssuedAt {
467470
pub fn now() -> Self {
468-
Self(Utc::now().try_into().unwrap())
471+
Self(Utc::now().into())
469472
}
470473

471474
pub fn verify(&self, now: DateTime<Utc>) -> Result<(), JwtClaimValidationFailed> {

crates/claims/crates/jwt/src/datatype/numeric_date.rs

Lines changed: 16 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -4,18 +4,11 @@ use chrono::{prelude::*, Duration, LocalResult};
44
use ordered_float::NotNan;
55
use serde::{Deserialize, Serialize, Serializer};
66

7-
/// Represents NumericDate (see <https://datatracker.ietf.org/doc/html/rfc7519#section-2>)
8-
/// where the range is restricted to those in which microseconds can be exactly represented,
9-
/// which is approximately between the years 1685 and 2255, which was considered to be sufficient
10-
/// for the purposes of this crate. Note that leap seconds are ignored by this type, just as
11-
/// they're ignored by NumericDate in the JWT standard.
7+
/// JSON numeric value representing the number of seconds from
8+
/// 1970-01-01T00:00:00Z UTC until the specified UTC date/time, ignoring leap
9+
/// seconds.
1210
///
13-
/// An f64 value has 52 explicit mantissa bits, meaning that the biggest contiguous range
14-
/// of integer values is from -2^53 to 2^53 (52 zeros after the mantissa's implicit 1).
15-
/// Using this value to represent exact microseconds gives a maximum range of
16-
/// +-2^53 / (1000000 * 60 * 60 * 24 * 365.25) ~= +-285,
17-
/// which is centered around the Unix epoch start date Jan 1, 1970, 00:00:00 UTC, giving
18-
/// the years 1685 to 2255.
11+
/// See: <https://datatracker.ietf.org/doc/html/rfc7519#section-2>
1912
#[derive(Debug, Serialize, Deserialize, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
2013
pub struct NumericDate(#[serde(serialize_with = "interop_serialize")] NotNan<f64>);
2114

@@ -40,9 +33,6 @@ pub enum NumericDateConversionError {
4033

4134
#[error("Invalid float literal")]
4235
InvalidFloatLiteral,
43-
44-
#[error("Out of valid microsecond-precision range of NumericDate")]
45-
OutOfMicrosecondPrecisionRange,
4636
}
4737

4838
impl From<ordered_float::FloatIsNan> for NumericDateConversionError {
@@ -52,28 +42,17 @@ impl From<ordered_float::FloatIsNan> for NumericDateConversionError {
5242
}
5343

5444
impl NumericDate {
55-
/// This is -2^53 / 1_000_000, which is the smallest NumericDate that faithfully
56-
/// represents full microsecond precision.
57-
pub const MIN: NumericDate =
58-
NumericDate(unsafe { NotNan::new_unchecked(-9_007_199_254.740_992) });
59-
/// This is 2^53 / 1_000_000, which is the largest NumericDate that faithfully
60-
/// represents full microsecond precision.
61-
pub const MAX: NumericDate =
62-
NumericDate(unsafe { NotNan::new_unchecked(9_007_199_254.740_992) });
63-
6445
/// Return the f64-valued number of seconds represented by this NumericDate.
6546
pub fn as_seconds(self) -> f64 {
6647
*self.0
6748
}
49+
6850
/// Try to create NumericDate from a f64 value, returning error upon out-of-range.
6951
pub fn try_from_seconds(seconds: f64) -> Result<Self, NumericDateConversionError> {
7052
let seconds = NotNan::new(seconds)?;
71-
if seconds.abs() > *Self::MAX.0 {
72-
Err(NumericDateConversionError::OutOfMicrosecondPrecisionRange)
73-
} else {
74-
Ok(NumericDate(seconds))
75-
}
53+
Ok(NumericDate(seconds))
7654
}
55+
7756
/// Decompose NumericDate for use in Utc.timestamp and Utc.timestamp_opt
7857
fn into_whole_seconds_and_fractional_nanoseconds(self) -> (i64, u32) {
7958
let whole_seconds = self.0.floor() as i64;
@@ -88,7 +67,7 @@ impl std::ops::Add<Duration> for NumericDate {
8867
type Output = NumericDate;
8968
fn add(self, rhs: Duration) -> Self::Output {
9069
let self_dtu: DateTime<Utc> = self.into();
91-
Self::Output::try_from(self_dtu + rhs).unwrap()
70+
Self::Output::from(self_dtu + rhs)
9271
}
9372
}
9473

@@ -107,7 +86,7 @@ impl std::ops::Sub<Duration> for NumericDate {
10786
type Output = NumericDate;
10887
fn sub(self, rhs: Duration) -> Self::Output {
10988
let self_dtu: DateTime<Utc> = self.into();
110-
Self::Output::try_from(self_dtu - rhs).unwrap()
89+
Self::Output::from(self_dtu - rhs)
11190
}
11291
}
11392

@@ -133,10 +112,8 @@ impl TryFrom<f64> for NumericDate {
133112
}
134113
}
135114

136-
impl TryFrom<DateTime<Utc>> for NumericDate {
137-
type Error = NumericDateConversionError;
138-
139-
fn try_from(dtu: DateTime<Utc>) -> Result<Self, Self::Error> {
115+
impl From<DateTime<Utc>> for NumericDate {
116+
fn from(dtu: DateTime<Utc>) -> Self {
140117
// Have to take seconds and nanoseconds separately in order to get the full allowable
141118
// range of microsecond-precision values as described above.
142119
let whole_seconds = dtu.timestamp() as f64;
@@ -146,14 +123,14 @@ impl TryFrom<DateTime<Utc>> for NumericDate {
146123
};
147124

148125
Self::try_from_seconds(whole_seconds + fractional_seconds)
126+
// UNWRAP SAFETY: input value can't be NaN nor infinite.
127+
.unwrap()
149128
}
150129
}
151130

152-
impl TryFrom<DateTime<FixedOffset>> for NumericDate {
153-
type Error = NumericDateConversionError;
154-
fn try_from(dtfo: DateTime<FixedOffset>) -> Result<Self, Self::Error> {
155-
let dtu = DateTime::<Utc>::from(dtfo);
156-
NumericDate::try_from(dtu)
131+
impl From<DateTime<FixedOffset>> for NumericDate {
132+
fn from(dtfo: DateTime<FixedOffset>) -> Self {
133+
DateTime::<Utc>::from(dtfo).into()
157134
}
158135
}
159136

crates/claims/crates/vc/src/v1/jwt/encode.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ pub fn encode_jwt_vc_claims<T: Serialize>(
4444
let date_value: xsd_types::DateTime = date_value
4545
.parse()
4646
.map_err(|_| JwtVcEncodeError::InvalidDateValue)?;
47-
claims.set(ssi_jwt::ExpirationTime(date_value.earliest().try_into()?));
47+
claims.set(ssi_jwt::ExpirationTime(date_value.earliest().into()));
4848
}
4949
None => return Err(JwtVcEncodeError::InvalidDateValue),
5050
}
@@ -65,7 +65,7 @@ pub fn encode_jwt_vc_claims<T: Serialize>(
6565
let issuance_date_value: xsd_types::DateTime = issuance_date_value
6666
.parse()
6767
.map_err(|_| JwtVcEncodeError::InvalidDateValue)?;
68-
claims.set(ssi_jwt::NotBefore(issuance_date_value.latest().try_into()?));
68+
claims.set(ssi_jwt::NotBefore(issuance_date_value.latest().into()));
6969
}
7070
None => return Err(JwtVcEncodeError::InvalidDateValue),
7171
}
@@ -145,7 +145,7 @@ pub fn encode_jwt_vp_claims<T: Serialize>(
145145
let issuance_date_value: xsd_types::DateTime = issuance_date_value
146146
.parse()
147147
.map_err(|_| JwtVpEncodeError::InvalidDateValue)?;
148-
claims.set(ssi_jwt::NotBefore(issuance_date_value.latest().try_into()?));
148+
claims.set(ssi_jwt::NotBefore(issuance_date_value.latest().into()));
149149
}
150150
None => return Err(JwtVpEncodeError::InvalidDateValue),
151151
}

0 commit comments

Comments
 (0)