summaryrefslogtreecommitdiffstats
path: root/src/auth/claims.rs
diff options
context:
space:
mode:
authorToby Vincent <tobyv@tobyvin.dev>2024-04-16 12:21:37 -0500
committerToby Vincent <tobyv@tobyvin.dev>2024-04-16 12:21:37 -0500
commit469cbc20853bcae0e74922f16f7a969d1b7a9a67 (patch)
tree6669218f0e4350a7a7050961e432bf80512b3551 /src/auth/claims.rs
parent940de575ca40e906c714c6b4ae1d1cbb6b4fc3d6 (diff)
refactor: store claim fields as OffsetDateTime
Diffstat (limited to 'src/auth/claims.rs')
-rw-r--r--src/auth/claims.rs64
1 files changed, 49 insertions, 15 deletions
diff --git a/src/auth/claims.rs b/src/auth/claims.rs
index bee1c35..20a4d3f 100644
--- a/src/auth/claims.rs
+++ b/src/auth/claims.rs
@@ -15,7 +15,7 @@ use axum_extra::{
TypedHeader,
};
use serde::{Deserialize, Deserializer, Serialize, Serializer};
-use time::OffsetDateTime;
+use time::{Duration, OffsetDateTime};
use uuid::Uuid;
use super::{Error, JWT};
@@ -24,20 +24,31 @@ use super::{Error, JWT};
#[serde(remote = "Self")]
pub struct Claims<const LIFETIME: i64 = ACCESS> {
pub sub: Uuid,
- pub iat: i64,
- pub exp: i64,
- pub jti: Uuid,
+ #[serde(with = "numeric_date")]
+ iat: OffsetDateTime,
+ #[serde(with = "numeric_date")]
+ exp: OffsetDateTime,
+ jti: Uuid,
}
impl<const LIFETIME: i64> Claims<LIFETIME> {
- pub fn new(uuid: Uuid) -> Self {
- let now = OffsetDateTime::now_utc().unix_timestamp();
- Self {
- sub: uuid,
- iat: now,
- exp: now + LIFETIME,
- jti: uuid::Uuid::new_v4(),
- }
+ pub fn new(sub: Uuid, iat: OffsetDateTime) -> Self {
+ let iat = iat
+ .replace_millisecond(0)
+ .expect("Failed to remove millisecond from datetime. This should not have happened.");
+
+ let exp = iat + Duration::new(LIFETIME, 0);
+ let jti = uuid::Uuid::new_v4();
+
+ Self { sub, iat, exp, jti }
+ }
+
+ pub fn issue(uuid: Uuid) -> Self {
+ Self::new(uuid, OffsetDateTime::now_utc())
+ }
+
+ pub fn expired(&self) -> bool {
+ self.exp > OffsetDateTime::now_utc()
}
}
@@ -57,7 +68,7 @@ impl<'de, const LIFETIME: i64> Deserialize<'de> for Claims<LIFETIME> {
{
let claims = Self::deserialize(deserializer)?;
- if claims.exp - claims.iat != LIFETIME {
+ if claims.exp - claims.iat != Duration::new(LIFETIME, 0) {
return Err(serde::de::Error::custom(
"Lifetime is invalid for Claim type",
));
@@ -67,6 +78,29 @@ impl<'de, const LIFETIME: i64> Deserialize<'de> for Claims<LIFETIME> {
}
}
+mod numeric_date {
+ //! Custom serialization of OffsetDateTime to conform with the JWT spec (RFC 7519 section 2, "Numeric Date")
+ use serde::{self, Deserialize, Deserializer, Serializer};
+ use time::OffsetDateTime;
+
+ /// Serializes an OffsetDateTime to a Unix timestamp (milliseconds since 1970/1/1T00:00:00T)
+ pub fn serialize<S>(date: &OffsetDateTime, serializer: S) -> Result<S::Ok, S::Error>
+ where
+ S: Serializer,
+ {
+ serializer.serialize_i64(date.unix_timestamp())
+ }
+
+ /// Attempts to deserialize an i64 and use as a Unix timestamp
+ pub fn deserialize<'de, D>(deserializer: D) -> Result<OffsetDateTime, D::Error>
+ where
+ D: Deserializer<'de>,
+ {
+ OffsetDateTime::from_unix_timestamp(i64::deserialize(deserializer)?)
+ .map_err(|_| serde::de::Error::custom("invalid Unix timestamp value"))
+ }
+}
+
// 1 day in seconds
const ACCESS: i64 = 86400;
@@ -74,7 +108,7 @@ pub type AccessClaims = Claims<ACCESS>;
impl From<RefreshClaims> for AccessClaims {
fn from(value: RefreshClaims) -> Self {
- Claims::new(value.sub)
+ Claims::issue(value.sub)
}
}
@@ -83,7 +117,7 @@ impl TryFrom<AccessClaims> for Cookie<'_> {
fn try_from(value: AccessClaims) -> Result<Self, Self::Error> {
Ok(Cookie::build(("token", JWT.encode(&value)?))
- .expires(OffsetDateTime::from_unix_timestamp(value.exp)?)
+ .expires(value.exp)
.secure(true)
.http_only(true)
.build())