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