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