1use std::borrow::Cow;
2use std::collections::HashSet;
3use std::fmt;
4use std::marker::PhantomData;
5
6use serde::de::{self, Visitor};
7use serde::{Deserialize, Deserializer};
8
9use crate::algorithms::{Algorithm, AlgorithmFamily};
10use crate::errors::{ErrorKind, Result, new_error};
11
12#[derive(Debug, Clone, PartialEq, Eq)]
31pub struct Validation {
32 pub required_spec_claims: HashSet<String>,
39 pub leeway: u64,
44 pub reject_tokens_expiring_in_less_than: u64,
51 pub validate_exp: bool,
57 pub validate_nbf: bool,
66 pub validate_aud: bool,
75 pub aud: Option<HashSet<String>>,
84 pub iss: Option<HashSet<String>>,
93 pub sub: Option<String>,
101 pub algorithms: Vec<Algorithm>,
106}
107
108impl Validation {
109 pub fn new(alg: Algorithm) -> Validation {
111 Self::new_impl(vec![alg])
112 }
113
114 pub fn new_for_family(family: AlgorithmFamily) -> Validation {
116 Self::new_impl(family.algorithms().to_vec())
117 }
118
119 fn new_impl(algorithms: Vec<Algorithm>) -> Validation {
120 let mut required_claims = HashSet::with_capacity(1);
121 required_claims.insert("exp".to_owned());
122
123 Validation {
124 required_spec_claims: required_claims,
125 algorithms,
126 leeway: 60,
127 reject_tokens_expiring_in_less_than: 0,
128
129 validate_exp: true,
130 validate_nbf: false,
131 validate_aud: true,
132
133 iss: None,
134 sub: None,
135 aud: None,
136 }
137 }
138
139 pub fn set_audience<T: ToString>(&mut self, items: &[T]) {
142 self.aud = Some(items.iter().map(|x| x.to_string()).collect());
143 }
144
145 pub fn set_issuer<T: ToString>(&mut self, items: &[T]) {
148 self.iss = Some(items.iter().map(|x| x.to_string()).collect());
149 }
150
151 pub fn set_required_spec_claims<T: ToString>(&mut self, items: &[T]) {
157 self.required_spec_claims = items.iter().map(|x| x.to_string()).collect();
158 }
159}
160
161impl Default for Validation {
162 fn default() -> Self {
163 Self::new(Algorithm::HS256)
164 }
165}
166
167#[cfg(not(all(target_arch = "wasm32", not(any(target_os = "emscripten", target_os = "wasi")))))]
169#[must_use]
170pub fn get_current_timestamp() -> u64 {
171 let start = std::time::SystemTime::now();
172 start.duration_since(std::time::UNIX_EPOCH).expect("Time went backwards").as_secs()
173}
174
175#[cfg(all(target_arch = "wasm32", not(any(target_os = "emscripten", target_os = "wasi"))))]
177#[must_use]
178pub fn get_current_timestamp() -> u64 {
179 js_sys::Date::new_0().get_time() as u64 / 1000
180}
181
182#[derive(Deserialize)]
183pub(crate) struct ClaimsForValidation<'a> {
184 #[serde(deserialize_with = "numeric_type", default)]
185 exp: TryParse<u64>,
186 #[serde(deserialize_with = "numeric_type", default)]
187 nbf: TryParse<u64>,
188 #[serde(borrow)]
189 sub: TryParse<Cow<'a, str>>,
190 #[serde(borrow)]
191 iss: TryParse<Issuer<'a>>,
192 #[serde(borrow)]
193 aud: TryParse<Audience<'a>>,
194}
195
196#[derive(Default, Debug)]
197enum TryParse<T> {
198 Parsed(T),
199 FailedToParse,
200 #[default]
201 NotPresent,
202}
203
204impl<'de, T: Deserialize<'de>> Deserialize<'de> for TryParse<T> {
205 fn deserialize<D: serde::Deserializer<'de>>(
206 deserializer: D,
207 ) -> std::result::Result<Self, D::Error> {
208 Ok(match Option::<T>::deserialize(deserializer) {
209 Ok(Some(value)) => TryParse::Parsed(value),
210 Ok(None) => TryParse::NotPresent,
211 Err(_) => TryParse::FailedToParse,
212 })
213 }
214}
215
216#[derive(Deserialize)]
217#[serde(untagged)]
218enum Audience<'a> {
219 Single(#[serde(borrow)] Cow<'a, str>),
220 Multiple(#[serde(borrow)] HashSet<BorrowedCowIfPossible<'a>>),
221}
222
223#[derive(Deserialize)]
224#[serde(untagged)]
225enum Issuer<'a> {
226 Single(#[serde(borrow)] Cow<'a, str>),
227 Multiple(#[serde(borrow)] HashSet<BorrowedCowIfPossible<'a>>),
228}
229
230#[derive(Deserialize, PartialEq, Eq, Hash)]
234struct BorrowedCowIfPossible<'a>(#[serde(borrow)] Cow<'a, str>);
235
236impl std::borrow::Borrow<str> for BorrowedCowIfPossible<'_> {
237 fn borrow(&self) -> &str {
238 &self.0
239 }
240}
241
242fn is_subset(reference: &HashSet<String>, given: &HashSet<BorrowedCowIfPossible<'_>>) -> bool {
243 if reference.len() < given.len() {
245 reference.iter().any(|a| given.contains(&**a))
246 } else {
247 given.iter().any(|a| reference.contains(&*a.0))
248 }
249}
250
251pub(crate) fn validate(claims: ClaimsForValidation, options: &Validation) -> Result<()> {
252 for required_claim in &options.required_spec_claims {
253 let present = match required_claim.as_str() {
254 "exp" => matches!(claims.exp, TryParse::Parsed(_)),
255 "sub" => matches!(claims.sub, TryParse::Parsed(_)),
256 "iss" => matches!(claims.iss, TryParse::Parsed(_)),
257 "aud" => matches!(claims.aud, TryParse::Parsed(_)),
258 "nbf" => matches!(claims.nbf, TryParse::Parsed(_)),
259 _ => continue,
260 };
261
262 if !present {
263 return Err(new_error(ErrorKind::MissingRequiredClaim(required_claim.clone())));
264 }
265 }
266
267 if options.validate_exp || options.validate_nbf {
268 let now = get_current_timestamp();
269
270 if options.validate_exp && matches!(claims.exp, TryParse::FailedToParse) {
272 return Err(new_error(ErrorKind::InvalidClaimFormat("exp".to_string())));
273 }
274 if options.validate_nbf && matches!(claims.nbf, TryParse::FailedToParse) {
275 return Err(new_error(ErrorKind::InvalidClaimFormat("nbf".to_string())));
276 }
277
278 if matches!(claims.exp, TryParse::Parsed(exp) if exp < options.reject_tokens_expiring_in_less_than)
279 {
280 return Err(new_error(ErrorKind::InvalidToken));
281 }
282
283 if matches!(claims.exp, TryParse::Parsed(exp) if options.validate_exp
284 && exp - options.reject_tokens_expiring_in_less_than < now - options.leeway)
285 {
286 return Err(new_error(ErrorKind::ExpiredSignature));
287 }
288
289 if matches!(claims.nbf, TryParse::Parsed(nbf) if options.validate_nbf && nbf > now + options.leeway)
290 {
291 return Err(new_error(ErrorKind::ImmatureSignature));
292 }
293 }
294
295 if let (TryParse::Parsed(sub), Some(correct_sub)) = (claims.sub, options.sub.as_deref())
296 && sub != correct_sub
297 {
298 return Err(new_error(ErrorKind::InvalidSubject));
299 }
300
301 match (claims.iss, options.iss.as_ref()) {
302 (TryParse::Parsed(Issuer::Single(iss)), Some(correct_iss))
303 if !correct_iss.contains(&*iss) =>
304 {
305 return Err(new_error(ErrorKind::InvalidIssuer));
306 }
307 (TryParse::Parsed(Issuer::Multiple(iss)), Some(correct_iss))
308 if !is_subset(correct_iss, &iss) =>
309 {
310 return Err(new_error(ErrorKind::InvalidIssuer));
311 }
312 _ => {}
313 }
314
315 if !options.validate_aud {
316 return Ok(());
317 }
318 match (claims.aud, options.aud.as_ref()) {
319 (TryParse::Parsed(Audience::Multiple(aud)), None) if !aud.is_empty() => {
325 return Err(new_error(ErrorKind::InvalidAudience));
326 }
327 (TryParse::Parsed(_), None) => {
328 return Err(new_error(ErrorKind::InvalidAudience));
329 }
330 (TryParse::Parsed(Audience::Single(aud)), Some(correct_aud))
331 if !correct_aud.contains(&*aud) =>
332 {
333 return Err(new_error(ErrorKind::InvalidAudience));
334 }
335 (TryParse::Parsed(Audience::Multiple(aud)), Some(correct_aud))
336 if !is_subset(correct_aud, &aud) =>
337 {
338 return Err(new_error(ErrorKind::InvalidAudience));
339 }
340 _ => {}
341 }
342
343 Ok(())
344}
345
346fn numeric_type<'de, D>(deserializer: D) -> std::result::Result<TryParse<u64>, D::Error>
347where
348 D: Deserializer<'de>,
349{
350 struct NumericType(PhantomData<fn() -> TryParse<u64>>);
351
352 impl Visitor<'_> for NumericType {
353 type Value = TryParse<u64>;
354
355 fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
356 formatter.write_str("A NumericType that can be reasonably coerced into a u64")
357 }
358
359 fn visit_u64<E>(self, value: u64) -> std::result::Result<Self::Value, E>
360 where
361 E: de::Error,
362 {
363 Ok(TryParse::Parsed(value))
364 }
365
366 fn visit_f64<E>(self, value: f64) -> std::result::Result<Self::Value, E>
367 where
368 E: de::Error,
369 {
370 if value.is_finite() && value >= 0.0 && value < (u64::MAX as f64) {
371 Ok(TryParse::Parsed(value.round() as u64))
372 } else {
373 Err(serde::de::Error::custom("NumericType must be representable as a u64"))
374 }
375 }
376 }
377
378 match deserializer.deserialize_any(NumericType(PhantomData)) {
379 Ok(ok) => Ok(ok),
380 Err(_) => Ok(TryParse::FailedToParse),
381 }
382}
383
384#[cfg(test)]
385mod tests {
386 use std::collections::HashSet;
387
388 use serde_json::json;
389 use wasm_bindgen_test::wasm_bindgen_test;
390
391 use crate::Algorithm;
392 use crate::errors::ErrorKind;
393
394 use super::{ClaimsForValidation, Validation, get_current_timestamp, validate};
395
396 fn deserialize_claims(claims: &serde_json::Value) -> ClaimsForValidation<'_> {
397 serde::Deserialize::deserialize(claims).unwrap()
398 }
399
400 #[test]
401 #[wasm_bindgen_test]
402 fn exp_in_future_ok() {
403 let claims = json!({ "exp": get_current_timestamp() + 10000 });
404 let res = validate(deserialize_claims(&claims), &Validation::new(Algorithm::HS256));
405 assert!(res.is_ok());
406 }
407
408 #[test]
409 #[wasm_bindgen_test]
410 fn exp_in_future_but_in_rejection_period_fails() {
411 let claims = json!({ "exp": get_current_timestamp() + 500 });
412 let mut validation = Validation::new(Algorithm::HS256);
413 validation.leeway = 0;
414 validation.reject_tokens_expiring_in_less_than = 501;
415 let res = validate(deserialize_claims(&claims), &validation);
416 assert!(res.is_err());
417 }
418
419 #[test]
420 #[wasm_bindgen_test]
421 fn exp_float_in_future_ok() {
422 let claims = json!({ "exp": (get_current_timestamp() as f64) + 10000.123 });
423 let res = validate(deserialize_claims(&claims), &Validation::new(Algorithm::HS256));
424 assert!(res.is_ok());
425 }
426
427 #[test]
428 #[wasm_bindgen_test]
429 fn exp_float_in_future_but_in_rejection_period_fails() {
430 let claims = json!({ "exp": (get_current_timestamp() as f64) + 500.123 });
431 let mut validation = Validation::new(Algorithm::HS256);
432 validation.leeway = 0;
433 validation.reject_tokens_expiring_in_less_than = 501;
434 let res = validate(deserialize_claims(&claims), &validation);
435 assert!(res.is_err());
436 }
437
438 #[test]
439 #[wasm_bindgen_test]
440 fn exp_in_past_fails() {
441 let claims = json!({ "exp": get_current_timestamp() - 100000 });
442 let res = validate(deserialize_claims(&claims), &Validation::new(Algorithm::HS256));
443 assert!(res.is_err());
444
445 match res.unwrap_err().kind() {
446 ErrorKind::ExpiredSignature => (),
447 _ => unreachable!(),
448 };
449 }
450
451 #[test]
452 #[wasm_bindgen_test]
453 fn exp_float_in_past_fails() {
454 let claims = json!({ "exp": (get_current_timestamp() as f64) - 100000.1234 });
455 let res = validate(deserialize_claims(&claims), &Validation::new(Algorithm::HS256));
456 assert!(res.is_err());
457
458 match res.unwrap_err().kind() {
459 ErrorKind::ExpiredSignature => (),
460 _ => unreachable!(),
461 };
462 }
463
464 #[test]
465 #[wasm_bindgen_test]
466 fn exp_in_past_but_in_leeway_ok() {
467 let claims = json!({ "exp": get_current_timestamp() - 500 });
468 let mut validation = Validation::new(Algorithm::HS256);
469 validation.leeway = 1000 * 60;
470 let res = validate(deserialize_claims(&claims), &validation);
471 assert!(res.is_ok());
472 }
473
474 #[test]
476 #[wasm_bindgen_test]
477 fn validate_required_fields_are_present() {
478 for spec_claim in ["exp", "nbf", "aud", "iss", "sub"] {
479 let claims = json!({});
480 let mut validation = Validation::new(Algorithm::HS256);
481 validation.set_required_spec_claims(&[spec_claim]);
482 let res = validate(deserialize_claims(&claims), &validation).unwrap_err();
483 assert_eq!(res.kind(), &ErrorKind::MissingRequiredClaim(spec_claim.to_owned()));
484 }
485 }
486
487 #[test]
488 #[wasm_bindgen_test]
489 fn exp_validated_but_not_required_ok() {
490 let claims = json!({});
491 let mut validation = Validation::new(Algorithm::HS256);
492 validation.required_spec_claims = HashSet::new();
493 validation.validate_exp = true;
494 let res = validate(deserialize_claims(&claims), &validation);
495 assert!(res.is_ok());
496 }
497
498 #[test]
499 #[wasm_bindgen_test]
500 fn exp_validated_but_not_required_fails() {
501 let claims = json!({ "exp": (get_current_timestamp() as f64) - 100000.1234 });
502 let mut validation = Validation::new(Algorithm::HS256);
503 validation.required_spec_claims = HashSet::new();
504 validation.validate_exp = true;
505 let res = validate(deserialize_claims(&claims), &validation);
506 assert!(res.is_err());
507 }
508
509 #[test]
510 #[wasm_bindgen_test]
511 fn exp_required_but_not_validated_ok() {
512 let claims = json!({ "exp": (get_current_timestamp() as f64) - 100000.1234 });
513 let mut validation = Validation::new(Algorithm::HS256);
514 validation.set_required_spec_claims(&["exp"]);
515 validation.validate_exp = false;
516 let res = validate(deserialize_claims(&claims), &validation);
517 assert!(res.is_ok());
518 }
519
520 #[test]
521 #[wasm_bindgen_test]
522 fn exp_required_but_not_validated_fails() {
523 let claims = json!({});
524 let mut validation = Validation::new(Algorithm::HS256);
525 validation.set_required_spec_claims(&["exp"]);
526 validation.validate_exp = false;
527 let res = validate(deserialize_claims(&claims), &validation);
528 assert!(res.is_err());
529 }
530
531 #[test]
532 #[wasm_bindgen_test]
533 fn nbf_in_past_ok() {
534 let claims = json!({ "nbf": get_current_timestamp() - 10000 });
535 let mut validation = Validation::new(Algorithm::HS256);
536 validation.required_spec_claims = HashSet::new();
537 validation.validate_exp = false;
538 validation.validate_nbf = true;
539 let res = validate(deserialize_claims(&claims), &validation);
540 assert!(res.is_ok());
541 }
542
543 #[test]
544 #[wasm_bindgen_test]
545 fn nbf_float_in_past_ok() {
546 let claims = json!({ "nbf": (get_current_timestamp() as f64) - 10000.1234 });
547 let mut validation = Validation::new(Algorithm::HS256);
548 validation.required_spec_claims = HashSet::new();
549 validation.validate_exp = false;
550 validation.validate_nbf = true;
551 let res = validate(deserialize_claims(&claims), &validation);
552 assert!(res.is_ok());
553 }
554
555 #[test]
556 #[wasm_bindgen_test]
557 fn nbf_in_future_fails() {
558 let claims = json!({ "nbf": get_current_timestamp() + 100000 });
559 let mut validation = Validation::new(Algorithm::HS256);
560 validation.required_spec_claims = HashSet::new();
561 validation.validate_exp = false;
562 validation.validate_nbf = true;
563 let res = validate(deserialize_claims(&claims), &validation);
564 assert!(res.is_err());
565
566 match res.unwrap_err().kind() {
567 ErrorKind::ImmatureSignature => (),
568 _ => unreachable!(),
569 };
570 }
571
572 #[test]
573 #[wasm_bindgen_test]
574 fn nbf_in_future_but_in_leeway_ok() {
575 let claims = json!({ "nbf": get_current_timestamp() + 500 });
576 let mut validation = Validation::new(Algorithm::HS256);
577 validation.required_spec_claims = HashSet::new();
578 validation.validate_exp = false;
579 validation.validate_nbf = true;
580 validation.leeway = 1000 * 60;
581 let res = validate(deserialize_claims(&claims), &validation);
582 assert!(res.is_ok());
583 }
584
585 #[test]
586 #[wasm_bindgen_test]
587 fn iss_string_ok() {
588 let claims = json!({"iss": ["Keats"]});
589 let mut validation = Validation::new(Algorithm::HS256);
590 validation.required_spec_claims = HashSet::new();
591 validation.validate_exp = false;
592 validation.set_issuer(&["Keats"]);
593 let res = validate(deserialize_claims(&claims), &validation);
594 assert!(res.is_ok());
595 }
596
597 #[test]
598 #[wasm_bindgen_test]
599 fn iss_array_of_string_ok() {
600 let claims = json!({"iss": ["UserA", "UserB"]});
601 let mut validation = Validation::new(Algorithm::HS256);
602 validation.required_spec_claims = HashSet::new();
603 validation.validate_exp = false;
604 validation.set_issuer(&["UserA", "UserB"]);
605 let res = validate(deserialize_claims(&claims), &validation);
606 assert!(res.is_ok());
607 }
608
609 #[test]
610 #[wasm_bindgen_test]
611 fn iss_not_matching_fails() {
612 let claims = json!({"iss": "Hacked"});
613
614 let mut validation = Validation::new(Algorithm::HS256);
615 validation.required_spec_claims = HashSet::new();
616 validation.validate_exp = false;
617 validation.set_issuer(&["Keats"]);
618 let res = validate(deserialize_claims(&claims), &validation);
619 assert!(res.is_err());
620
621 match res.unwrap_err().kind() {
622 ErrorKind::InvalidIssuer => (),
623 _ => unreachable!(),
624 };
625 }
626
627 #[test]
628 #[wasm_bindgen_test]
629 fn iss_missing_fails() {
630 let claims = json!({});
631
632 let mut validation = Validation::new(Algorithm::HS256);
633 validation.set_required_spec_claims(&["iss"]);
634 validation.validate_exp = false;
635 validation.set_issuer(&["Keats"]);
636 let res = validate(deserialize_claims(&claims), &validation);
637
638 match res.unwrap_err().kind() {
639 ErrorKind::MissingRequiredClaim(claim) => assert_eq!(claim, "iss"),
640 _ => unreachable!(),
641 };
642 }
643
644 #[test]
645 #[wasm_bindgen_test]
646 fn sub_ok() {
647 let claims = json!({"sub": "Keats"});
648 let mut validation = Validation::new(Algorithm::HS256);
649 validation.required_spec_claims = HashSet::new();
650 validation.validate_exp = false;
651 validation.sub = Some("Keats".to_owned());
652 let res = validate(deserialize_claims(&claims), &validation);
653 assert!(res.is_ok());
654 }
655
656 #[test]
657 #[wasm_bindgen_test]
658 fn sub_not_matching_fails() {
659 let claims = json!({"sub": "Hacked"});
660 let mut validation = Validation::new(Algorithm::HS256);
661 validation.required_spec_claims = HashSet::new();
662 validation.validate_exp = false;
663 validation.sub = Some("Keats".to_owned());
664 let res = validate(deserialize_claims(&claims), &validation);
665 assert!(res.is_err());
666
667 match res.unwrap_err().kind() {
668 ErrorKind::InvalidSubject => (),
669 _ => unreachable!(),
670 };
671 }
672
673 #[test]
674 #[wasm_bindgen_test]
675 fn sub_missing_fails() {
676 let claims = json!({});
677 let mut validation = Validation::new(Algorithm::HS256);
678 validation.validate_exp = false;
679 validation.set_required_spec_claims(&["sub"]);
680 validation.sub = Some("Keats".to_owned());
681 let res = validate(deserialize_claims(&claims), &validation);
682 assert!(res.is_err());
683
684 match res.unwrap_err().kind() {
685 ErrorKind::MissingRequiredClaim(claim) => assert_eq!(claim, "sub"),
686 _ => unreachable!(),
687 };
688 }
689
690 #[test]
691 #[wasm_bindgen_test]
692 fn aud_string_ok() {
693 let claims = json!({"aud": "Everyone"});
694 let mut validation = Validation::new(Algorithm::HS256);
695 validation.validate_exp = false;
696 validation.required_spec_claims = HashSet::new();
697 validation.set_audience(&["Everyone"]);
698 let res = validate(deserialize_claims(&claims), &validation);
699 assert!(res.is_ok());
700 }
701
702 #[test]
703 #[wasm_bindgen_test]
704 fn aud_array_of_string_ok() {
705 let claims = json!({"aud": ["UserA", "UserB"]});
706 let mut validation = Validation::new(Algorithm::HS256);
707 validation.validate_exp = false;
708 validation.required_spec_claims = HashSet::new();
709 validation.set_audience(&["UserA", "UserB"]);
710 let res = validate(deserialize_claims(&claims), &validation);
711 assert!(res.is_ok());
712 }
713
714 #[test]
715 #[wasm_bindgen_test]
716 fn aud_type_mismatch_fails() {
717 let claims = json!({"aud": ["Everyone"]});
718 let mut validation = Validation::new(Algorithm::HS256);
719 validation.validate_exp = false;
720 validation.required_spec_claims = HashSet::new();
721 validation.set_audience(&["UserA", "UserB"]);
722 let res = validate(deserialize_claims(&claims), &validation);
723 assert!(res.is_err());
724
725 match res.unwrap_err().kind() {
726 ErrorKind::InvalidAudience => (),
727 _ => unreachable!(),
728 };
729 }
730
731 #[test]
732 #[wasm_bindgen_test]
733 fn aud_correct_type_not_matching_fails() {
734 let claims = json!({"aud": ["Everyone"]});
735 let mut validation = Validation::new(Algorithm::HS256);
736 validation.validate_exp = false;
737 validation.required_spec_claims = HashSet::new();
738 validation.set_audience(&["None"]);
739 let res = validate(deserialize_claims(&claims), &validation);
740 assert!(res.is_err());
741
742 match res.unwrap_err().kind() {
743 ErrorKind::InvalidAudience => (),
744 _ => unreachable!(),
745 };
746 }
747
748 #[test]
749 #[wasm_bindgen_test]
750 fn aud_none_fails() {
751 let claims = json!({"aud": ["Everyone"]});
752 let mut validation = Validation::new(Algorithm::HS256);
753 validation.validate_exp = false;
754 validation.required_spec_claims = HashSet::new();
755 validation.aud = None;
756 let res = validate(deserialize_claims(&claims), &validation);
757 assert!(res.is_err());
758
759 match res.unwrap_err().kind() {
760 ErrorKind::InvalidAudience => (),
761 _ => unreachable!(),
762 };
763 }
764
765 #[test]
766 #[wasm_bindgen_test]
767 fn aud_validation_skipped() {
768 let claims = json!({"aud": ["Everyone"]});
769 let mut validation = Validation::new(Algorithm::HS256);
770 validation.validate_exp = false;
771 validation.validate_aud = false;
772 validation.required_spec_claims = HashSet::new();
773 validation.aud = None;
774 let res = validate(deserialize_claims(&claims), &validation);
775 assert!(res.is_ok());
776 }
777
778 #[test]
779 #[wasm_bindgen_test]
780 fn aud_missing_fails() {
781 let claims = json!({});
782 let mut validation = Validation::new(Algorithm::HS256);
783 validation.validate_exp = false;
784 validation.set_required_spec_claims(&["aud"]);
785 validation.set_audience(&["None"]);
786 let res = validate(deserialize_claims(&claims), &validation);
787 assert!(res.is_err());
788
789 match res.unwrap_err().kind() {
790 ErrorKind::MissingRequiredClaim(claim) => assert_eq!(claim, "aud"),
791 _ => unreachable!(),
792 };
793 }
794
795 #[test]
797 #[wasm_bindgen_test]
798 fn does_validation_in_right_order() {
799 let claims = json!({ "exp": get_current_timestamp() + 10000 });
800
801 let mut validation = Validation::new(Algorithm::HS256);
802 validation.set_required_spec_claims(&["exp", "iss"]);
803 validation.leeway = 5;
804 validation.set_issuer(&["iss no check"]);
805 validation.set_audience(&["iss no check"]);
806
807 let res = validate(deserialize_claims(&claims), &validation);
808 assert!(res.is_err());
810 match res.unwrap_err().kind() {
811 ErrorKind::MissingRequiredClaim(claim) => assert_eq!(claim, "iss"),
812 t => panic!("{:?}", t),
813 };
814 }
815
816 #[test]
818 #[wasm_bindgen_test]
819 fn aud_use_validation_struct() {
820 let claims = json!({"aud": "my-googleclientid1234.apps.googleusercontent.com"});
821
822 let aud = "my-googleclientid1234.apps.googleusercontent.com".to_string();
823 let mut aud_hashset = std::collections::HashSet::new();
824 aud_hashset.insert(aud);
825 let mut validation = Validation::new(Algorithm::HS256);
826 validation.validate_exp = false;
827 validation.required_spec_claims = HashSet::new();
828 validation.set_audience(&["my-googleclientid1234.apps.googleusercontent.com"]);
829
830 let res = validate(deserialize_claims(&claims), &validation);
831 assert!(res.is_ok());
832 }
833
834 #[test]
836 #[wasm_bindgen_test]
837 fn doesnt_panic_with_leeway_overflow() {
838 let claims = json!({ "exp": 1 });
839
840 let mut validation = Validation::new(Algorithm::HS256);
841 validation.reject_tokens_expiring_in_less_than = 100;
842
843 let res = validate(deserialize_claims(&claims), &validation);
844 assert!(res.is_err());
845 }
846}