jwt_compact_preview/
claims.rs1use chrono::{DateTime, Duration, Utc};
2use serde::{Deserialize, Serialize};
3
4use crate::ValidationError;
5
6#[derive(Debug, Clone, Copy)]
8pub struct TimeOptions {
9 pub leeway: Duration,
11 pub current_time: Option<DateTime<Utc>>,
13}
14
15impl Default for TimeOptions {
16 fn default() -> Self {
17 Self {
18 leeway: Duration::seconds(60),
19 current_time: None,
20 }
21 }
22}
23
24#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash, Serialize, Deserialize)]
26pub struct Empty {}
27
28#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
36pub struct Claims<T> {
37 #[serde(
39 rename = "exp",
40 default,
41 skip_serializing_if = "Option::is_none",
42 with = "self::serde_timestamp"
43 )]
44 pub expiration_date: Option<DateTime<Utc>>,
45
46 #[serde(
48 rename = "nbf",
49 default,
50 skip_serializing_if = "Option::is_none",
51 with = "self::serde_timestamp"
52 )]
53 pub not_before: Option<DateTime<Utc>>,
54
55 #[serde(
57 rename = "iat",
58 default,
59 skip_serializing_if = "Option::is_none",
60 with = "self::serde_timestamp"
61 )]
62 pub issued_at: Option<DateTime<Utc>>,
63
64 #[serde(flatten)]
66 pub custom: T,
67}
68
69impl Claims<Empty> {
70 pub fn empty() -> Self {
72 Self {
73 expiration_date: None,
74 not_before: None,
75 issued_at: None,
76 custom: Empty {},
77 }
78 }
79}
80
81impl<T> Claims<T> {
82 pub fn new(custom_claims: T) -> Self {
84 Self {
85 expiration_date: None,
86 not_before: None,
87 issued_at: None,
88 custom: custom_claims,
89 }
90 }
91
92 pub fn set_duration(self, duration: Duration) -> Self {
94 Self {
95 expiration_date: Some(Utc::now() + duration),
96 ..self
97 }
98 }
99
100 pub fn set_duration_and_issuance(self, duration: Duration) -> Self {
103 let issued_at = Utc::now();
104 Self {
105 expiration_date: Some(issued_at + duration),
106 issued_at: Some(issued_at),
107 ..self
108 }
109 }
110
111 pub fn set_not_before(self, moment: DateTime<Utc>) -> Self {
113 Self {
114 not_before: Some(moment),
115 ..self
116 }
117 }
118
119 pub fn validate_expiration(&self, options: TimeOptions) -> Result<&Self, ValidationError> {
124 if let Some(expiration) = self.expiration_date {
125 let current_time = options.current_time.unwrap_or_else(Utc::now);
126 if current_time > expiration + options.leeway {
127 Err(ValidationError::Expired)
128 } else {
129 Ok(self)
130 }
131 } else {
132 Err(ValidationError::NoClaim)
133 }
134 }
135
136 pub fn validate_maturity(&self, options: TimeOptions) -> Result<&Self, ValidationError> {
141 if let Some(not_before) = self.not_before {
142 let current_time = options.current_time.unwrap_or_else(Utc::now);
143 if current_time < not_before - options.leeway {
144 Err(ValidationError::NotMature)
145 } else {
146 Ok(self)
147 }
148 } else {
149 Err(ValidationError::NoClaim)
150 }
151 }
152}
153
154mod serde_timestamp {
155 use chrono::{offset::TimeZone, DateTime, Utc};
156 use serde::{
157 de::{Error as DeError, Visitor},
158 Deserializer, Serializer,
159 };
160
161 use std::{convert::TryFrom, fmt};
162
163 struct TimestampVisitor;
164
165 impl<'de> Visitor<'de> for TimestampVisitor {
166 type Value = DateTime<Utc>;
167
168 fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
169 formatter.write_str("UTC timestamp")
170 }
171
172 fn visit_i64<E>(self, value: i64) -> Result<Self::Value, E>
173 where
174 E: DeError,
175 {
176 Ok(Utc.timestamp(value, 0))
177 }
178
179 fn visit_u64<E>(self, value: u64) -> Result<Self::Value, E>
180 where
181 E: DeError,
182 {
183 let value = i64::try_from(value).map_err(DeError::custom)?;
184 Ok(Utc.timestamp(value, 0))
185 }
186 }
187
188 pub fn serialize<S: Serializer>(
189 time: &Option<DateTime<Utc>>,
190 serializer: S,
191 ) -> Result<S::Ok, S::Error> {
192 serializer.serialize_i64(time.unwrap().timestamp())
194 }
195
196 pub fn deserialize<'de, D: Deserializer<'de>>(
197 deserializer: D,
198 ) -> Result<Option<DateTime<Utc>>, D::Error> {
199 deserializer.deserialize_i64(TimestampVisitor).map(Some)
200 }
201}
202
203#[cfg(test)]
204mod tests {
205 use super::*;
206 use assert_matches::assert_matches;
207
208 #[test]
209 fn empty_claims_can_be_serialized() {
210 let mut claims = Claims::empty();
211 assert!(serde_json::to_string(&claims).is_ok());
212 assert!(serde_cbor::to_vec(&claims).is_ok());
213 claims.expiration_date = Some(Utc::now());
214 assert!(serde_json::to_string(&claims).is_ok());
215 assert!(serde_cbor::to_vec(&claims).is_ok());
216 claims.not_before = Some(Utc::now());
217 assert!(serde_json::to_string(&claims).is_ok());
218 assert!(serde_cbor::to_vec(&claims).is_ok());
219 }
220
221 #[test]
222 fn expired_claim() {
223 let mut claims = Claims::empty();
224 assert_matches!(
225 claims
226 .validate_expiration(TimeOptions::default())
227 .unwrap_err(),
228 ValidationError::NoClaim
229 );
230
231 claims.expiration_date = Some(Utc::now() - Duration::hours(1));
232 assert_matches!(
233 claims
234 .validate_expiration(TimeOptions::default())
235 .unwrap_err(),
236 ValidationError::Expired
237 );
238
239 claims.expiration_date = Some(Utc::now() - Duration::seconds(10));
240 assert!(claims.validate_expiration(TimeOptions::default()).is_ok());
242 assert_matches!(
244 claims
245 .validate_expiration(TimeOptions {
246 leeway: Duration::seconds(5),
247 ..Default::default()
248 })
249 .unwrap_err(),
250 ValidationError::Expired
251 );
252 }
253
254 #[test]
255 fn immature_claim() {
256 let mut claims = Claims::empty();
257 assert_matches!(
258 claims
259 .validate_maturity(TimeOptions::default())
260 .unwrap_err(),
261 ValidationError::NoClaim
262 );
263
264 claims.not_before = Some(Utc::now() + Duration::hours(1));
265 assert_matches!(
266 claims
267 .validate_maturity(TimeOptions::default())
268 .unwrap_err(),
269 ValidationError::NotMature
270 );
271
272 claims.not_before = Some(Utc::now() + Duration::seconds(10));
273 assert!(claims.validate_maturity(TimeOptions::default()).is_ok());
275 assert_matches!(
277 claims
278 .validate_maturity(TimeOptions {
279 leeway: Duration::seconds(5),
280 ..Default::default()
281 })
282 .unwrap_err(),
283 ValidationError::NotMature
284 );
285 }
286}