1use std::{collections::HashMap, convert::Infallible, marker::PhantomData, ops::Deref};
8
9use base64ct::{Base64UrlUnpadded, Encoding};
10use mas_iana::jose::JsonWebSignatureAlg;
11use serde::{Deserialize, Serialize, de::DeserializeOwned};
12use sha2::{Digest, Sha256, Sha384, Sha512};
13use thiserror::Error;
14
15#[derive(Debug, Error)]
16pub enum ClaimError {
17 #[error("missing claim {0:?}")]
18 MissingClaim(&'static str),
19
20 #[error("invalid claim {0:?}")]
21 InvalidClaim(&'static str),
22
23 #[error("could not validate claim {claim:?}")]
24 ValidationError {
25 claim: &'static str,
26 #[source]
27 source: Box<dyn std::error::Error + Send + Sync + 'static>,
28 },
29}
30
31pub trait Validator<T> {
32 type Error;
34
35 fn validate(&self, value: &T) -> Result<(), Self::Error>;
41}
42
43impl<T> Validator<T> for () {
44 type Error = Infallible;
45
46 fn validate(&self, _value: &T) -> Result<(), Self::Error> {
47 Ok(())
48 }
49}
50
51pub struct Claim<T, V = ()> {
52 claim: &'static str,
53 t: PhantomData<T>,
54 v: PhantomData<V>,
55}
56
57impl<T, V> Claim<T, V>
58where
59 V: Validator<T>,
60{
61 #[must_use]
62 pub const fn new(claim: &'static str) -> Self {
63 Self {
64 claim,
65 t: PhantomData,
66 v: PhantomData,
67 }
68 }
69
70 pub fn insert<I>(
76 &self,
77 claims: &mut HashMap<String, serde_json::Value>,
78 value: I,
79 ) -> Result<(), ClaimError>
80 where
81 I: Into<T>,
82 T: Serialize,
83 {
84 let value = value.into();
85 let value: serde_json::Value =
86 serde_json::to_value(&value).map_err(|_| ClaimError::InvalidClaim(self.claim))?;
87 claims.insert(self.claim.to_owned(), value);
88
89 Ok(())
90 }
91
92 pub fn extract_required(
99 &self,
100 claims: &mut HashMap<String, serde_json::Value>,
101 ) -> Result<T, ClaimError>
102 where
103 T: DeserializeOwned,
104 V: Default,
105 V::Error: std::error::Error + Send + Sync + 'static,
106 {
107 let validator = V::default();
108 self.extract_required_with_options(claims, validator)
109 }
110
111 pub fn extract_required_with_options<I>(
118 &self,
119 claims: &mut HashMap<String, serde_json::Value>,
120 validator: I,
121 ) -> Result<T, ClaimError>
122 where
123 T: DeserializeOwned,
124 I: Into<V>,
125 V::Error: std::error::Error + Send + Sync + 'static,
126 {
127 let validator: V = validator.into();
128 let claim = claims
129 .remove(self.claim)
130 .ok_or(ClaimError::MissingClaim(self.claim))?;
131
132 let res =
133 serde_json::from_value(claim).map_err(|_| ClaimError::InvalidClaim(self.claim))?;
134 validator
135 .validate(&res)
136 .map_err(|source| ClaimError::ValidationError {
137 claim: self.claim,
138 source: Box::new(source),
139 })?;
140 Ok(res)
141 }
142
143 pub fn extract_optional(
150 &self,
151 claims: &mut HashMap<String, serde_json::Value>,
152 ) -> Result<Option<T>, ClaimError>
153 where
154 T: DeserializeOwned,
155 V: Default,
156 V::Error: std::error::Error + Send + Sync + 'static,
157 {
158 let validator = V::default();
159 self.extract_optional_with_options(claims, validator)
160 }
161
162 pub fn extract_optional_with_options<I>(
170 &self,
171 claims: &mut HashMap<String, serde_json::Value>,
172 validator: I,
173 ) -> Result<Option<T>, ClaimError>
174 where
175 T: DeserializeOwned,
176 I: Into<V>,
177 V::Error: std::error::Error + Send + Sync + 'static,
178 {
179 match self.extract_required_with_options(claims, validator) {
180 Ok(v) => Ok(Some(v)),
181 Err(ClaimError::MissingClaim(_)) => Ok(None),
182 Err(e) => Err(e),
183 }
184 }
185}
186
187#[derive(Debug, Clone)]
188pub struct TimeOptions {
189 when: chrono::DateTime<chrono::Utc>,
190 leeway: chrono::Duration,
191}
192
193impl TimeOptions {
194 #[must_use]
195 pub fn new(when: chrono::DateTime<chrono::Utc>) -> Self {
196 Self {
197 when,
198 leeway: chrono::Duration::microseconds(5 * 60 * 1000 * 1000),
199 }
200 }
201
202 #[must_use]
203 pub fn leeway(mut self, leeway: chrono::Duration) -> Self {
204 self.leeway = leeway;
205 self
206 }
207}
208
209#[derive(Debug, Clone, Copy, Error)]
210#[error("Current time is too far away")]
211pub struct TimeTooFarError;
212
213#[derive(Debug, Clone)]
214pub struct TimeNotAfter(TimeOptions);
215
216impl Validator<Timestamp> for TimeNotAfter {
217 type Error = TimeTooFarError;
218 fn validate(&self, value: &Timestamp) -> Result<(), Self::Error> {
219 if self.0.when <= value.0 + self.0.leeway {
220 Ok(())
221 } else {
222 Err(TimeTooFarError)
223 }
224 }
225}
226
227impl From<TimeOptions> for TimeNotAfter {
228 fn from(opt: TimeOptions) -> Self {
229 Self(opt)
230 }
231}
232
233impl From<&TimeOptions> for TimeNotAfter {
234 fn from(opt: &TimeOptions) -> Self {
235 opt.clone().into()
236 }
237}
238
239#[derive(Debug, Clone)]
240pub struct TimeNotBefore(TimeOptions);
241
242impl Validator<Timestamp> for TimeNotBefore {
243 type Error = TimeTooFarError;
244
245 fn validate(&self, value: &Timestamp) -> Result<(), Self::Error> {
246 if self.0.when >= value.0 - self.0.leeway {
247 Ok(())
248 } else {
249 Err(TimeTooFarError)
250 }
251 }
252}
253
254impl From<TimeOptions> for TimeNotBefore {
255 fn from(opt: TimeOptions) -> Self {
256 Self(opt)
257 }
258}
259
260impl From<&TimeOptions> for TimeNotBefore {
261 fn from(opt: &TimeOptions) -> Self {
262 opt.clone().into()
263 }
264}
265
266pub fn hash_token(alg: &JsonWebSignatureAlg, token: &str) -> Result<String, TokenHashError> {
276 let bits = match alg {
277 JsonWebSignatureAlg::Hs256
278 | JsonWebSignatureAlg::Rs256
279 | JsonWebSignatureAlg::Es256
280 | JsonWebSignatureAlg::Ps256
281 | JsonWebSignatureAlg::Es256K => {
282 let mut hasher = Sha256::new();
283 hasher.update(token);
284 let hash: [u8; 32] = hasher.finalize().into();
285 hash[..16].to_owned()
287 }
288 JsonWebSignatureAlg::Hs384
289 | JsonWebSignatureAlg::Rs384
290 | JsonWebSignatureAlg::Es384
291 | JsonWebSignatureAlg::Ps384 => {
292 let mut hasher = Sha384::new();
293 hasher.update(token);
294 let hash: [u8; 48] = hasher.finalize().into();
295 hash[..24].to_owned()
297 }
298 JsonWebSignatureAlg::Hs512
299 | JsonWebSignatureAlg::Rs512
300 | JsonWebSignatureAlg::Es512
301 | JsonWebSignatureAlg::Ps512 => {
302 let mut hasher = Sha512::new();
303 hasher.update(token);
304 let hash: [u8; 64] = hasher.finalize().into();
305 hash[..32].to_owned()
307 }
308 _ => return Err(TokenHashError::UnsupportedAlgorithm),
309 };
310
311 Ok(Base64UrlUnpadded::encode_string(&bits))
312}
313
314#[derive(Debug, Clone, Copy, Error)]
315pub enum TokenHashError {
316 #[error("Hashes don't match")]
317 HashMismatch,
318
319 #[error("Unsupported algorithm for hashing")]
320 UnsupportedAlgorithm,
321}
322
323#[derive(Debug, Clone)]
324pub struct TokenHash<'a> {
325 alg: &'a JsonWebSignatureAlg,
326 token: &'a str,
327}
328
329impl<'a> TokenHash<'a> {
330 #[must_use]
332 pub fn new(alg: &'a JsonWebSignatureAlg, token: &'a str) -> Self {
333 Self { alg, token }
334 }
335}
336
337impl Validator<String> for TokenHash<'_> {
338 type Error = TokenHashError;
339 fn validate(&self, value: &String) -> Result<(), Self::Error> {
340 if hash_token(self.alg, self.token)? == *value {
341 Ok(())
342 } else {
343 Err(TokenHashError::HashMismatch)
344 }
345 }
346}
347
348#[derive(Debug, Clone, Copy, Error)]
349#[error("Values don't match")]
350pub struct EqualityError;
351
352#[derive(Debug, Clone)]
353pub struct Equality<'a, T: ?Sized> {
354 value: &'a T,
355}
356
357impl<'a, T: ?Sized> Equality<'a, T> {
358 #[must_use]
360 pub fn new(value: &'a T) -> Self {
361 Self { value }
362 }
363}
364
365impl<T1, T2> Validator<T1> for Equality<'_, T2>
366where
367 T2: PartialEq<T1> + ?Sized,
368{
369 type Error = EqualityError;
370 fn validate(&self, value: &T1) -> Result<(), Self::Error> {
371 if *self.value == *value {
372 Ok(())
373 } else {
374 Err(EqualityError)
375 }
376 }
377}
378
379impl<'a, T: ?Sized> From<&'a T> for Equality<'a, T> {
380 fn from(value: &'a T) -> Self {
381 Self::new(value)
382 }
383}
384
385#[derive(Debug, Clone)]
386pub struct Contains<'a, T> {
387 value: &'a T,
388}
389
390impl<'a, T> Contains<'a, T> {
391 #[must_use]
393 pub fn new(value: &'a T) -> Self {
394 Self { value }
395 }
396}
397
398#[derive(Debug, Clone, Copy, Error)]
399#[error("OneOrMany doesn't contain value")]
400pub struct ContainsError;
401
402impl<T> Validator<OneOrMany<T>> for Contains<'_, T>
403where
404 T: PartialEq,
405{
406 type Error = ContainsError;
407 fn validate(&self, value: &OneOrMany<T>) -> Result<(), Self::Error> {
408 if value.contains(self.value) {
409 Ok(())
410 } else {
411 Err(ContainsError)
412 }
413 }
414}
415
416impl<'a, T> From<&'a T> for Contains<'a, T> {
417 fn from(value: &'a T) -> Self {
418 Self::new(value)
419 }
420}
421
422#[derive(Deserialize, Serialize, Debug, Clone, PartialEq, Eq)]
423#[serde(transparent)]
424pub struct Timestamp(#[serde(with = "chrono::serde::ts_seconds")] chrono::DateTime<chrono::Utc>);
425
426impl Deref for Timestamp {
427 type Target = chrono::DateTime<chrono::Utc>;
428
429 fn deref(&self) -> &Self::Target {
430 &self.0
431 }
432}
433
434impl From<chrono::DateTime<chrono::Utc>> for Timestamp {
435 fn from(value: chrono::DateTime<chrono::Utc>) -> Self {
436 Timestamp(value)
437 }
438}
439
440#[derive(Deserialize, Serialize, Debug, Clone, PartialEq, Eq)]
441#[serde(
442 transparent,
443 bound(serialize = "T: Serialize", deserialize = "T: Deserialize<'de>")
444)]
445pub struct OneOrMany<T>(
446 #[serde(
450 with = "serde_with::As::<serde_with::OneOrMany<serde_with::Same, serde_with::formats::PreferOne>>"
451 )]
452 Vec<T>,
453);
454
455impl<T> Deref for OneOrMany<T> {
456 type Target = Vec<T>;
457
458 fn deref(&self) -> &Self::Target {
459 &self.0
460 }
461}
462
463impl<T> From<Vec<T>> for OneOrMany<T> {
464 fn from(value: Vec<T>) -> Self {
465 Self(value)
466 }
467}
468
469impl<T> From<T> for OneOrMany<T> {
470 fn from(value: T) -> Self {
471 Self(vec![value])
472 }
473}
474
475mod rfc7519 {
478 use super::{Claim, Contains, Equality, OneOrMany, TimeNotAfter, TimeNotBefore, Timestamp};
479
480 pub const ISS: Claim<String, Equality<str>> = Claim::new("iss");
481 pub const SUB: Claim<String> = Claim::new("sub");
482 pub const AUD: Claim<OneOrMany<String>, Contains<String>> = Claim::new("aud");
483 pub const NBF: Claim<Timestamp, TimeNotBefore> = Claim::new("nbf");
484 pub const EXP: Claim<Timestamp, TimeNotAfter> = Claim::new("exp");
485 pub const IAT: Claim<Timestamp, TimeNotBefore> = Claim::new("iat");
486 pub const JTI: Claim<String> = Claim::new("jti");
487}
488
489mod oidc_core {
493 use url::Url;
494
495 use super::{Claim, Equality, Timestamp, TokenHash};
496
497 pub const AUTH_TIME: Claim<Timestamp> = Claim::new("auth_time");
498 pub const NONCE: Claim<String, Equality<str>> = Claim::new("nonce");
499 pub const AT_HASH: Claim<String, TokenHash> = Claim::new("at_hash");
500 pub const C_HASH: Claim<String, TokenHash> = Claim::new("c_hash");
501
502 pub const NAME: Claim<String> = Claim::new("name");
503 pub const GIVEN_NAME: Claim<String> = Claim::new("given_name");
504 pub const FAMILY_NAME: Claim<String> = Claim::new("family_name");
505 pub const MIDDLE_NAME: Claim<String> = Claim::new("middle_name");
506 pub const NICKNAME: Claim<String> = Claim::new("nickname");
507 pub const PREFERRED_USERNAME: Claim<String> = Claim::new("preferred_username");
508 pub const PROFILE: Claim<Url> = Claim::new("profile");
509 pub const PICTURE: Claim<Url> = Claim::new("picture");
510 pub const WEBSITE: Claim<Url> = Claim::new("website");
511 pub const EMAIL: Claim<String> = Claim::new("email");
513 pub const EMAIL_VERIFIED: Claim<bool> = Claim::new("email_verified");
514 pub const GENDER: Claim<String> = Claim::new("gender");
515 pub const BIRTHDATE: Claim<String> = Claim::new("birthdate");
517 pub const ZONEINFO: Claim<String> = Claim::new("zoneinfo");
519 pub const LOCALE: Claim<String> = Claim::new("locale");
521 pub const PHONE_NUMBER: Claim<String> = Claim::new("phone_number");
523 pub const PHONE_NUMBER_VERIFIED: Claim<bool> = Claim::new("phone_number_verified");
524 pub const UPDATED_AT: Claim<Timestamp> = Claim::new("updated_at");
526}
527
528pub use self::{oidc_core::*, rfc7519::*};
529
530#[cfg(test)]
531mod tests {
532 use chrono::TimeZone;
533
534 use super::*;
535
536 #[test]
537 fn timestamp_serde() {
538 let datetime = Timestamp(
539 chrono::Utc
540 .with_ymd_and_hms(2018, 1, 18, 1, 30, 22)
541 .unwrap(),
542 );
543 let timestamp = serde_json::Value::Number(1_516_239_022.into());
544
545 assert_eq!(datetime, serde_json::from_value(timestamp.clone()).unwrap());
546 assert_eq!(timestamp, serde_json::to_value(&datetime).unwrap());
547 }
548
549 #[test]
550 fn one_or_many_serde() {
551 let one = OneOrMany(vec!["one".to_owned()]);
552 let many = OneOrMany(vec!["one".to_owned(), "two".to_owned()]);
553
554 assert_eq!(
555 one,
556 serde_json::from_value(serde_json::json!("one")).unwrap()
557 );
558 assert_eq!(
559 one,
560 serde_json::from_value(serde_json::json!(["one"])).unwrap()
561 );
562 assert_eq!(
563 many,
564 serde_json::from_value(serde_json::json!(["one", "two"])).unwrap()
565 );
566 assert_eq!(
567 serde_json::to_value(&one).unwrap(),
568 serde_json::json!("one")
569 );
570 assert_eq!(
571 serde_json::to_value(&many).unwrap(),
572 serde_json::json!(["one", "two"])
573 );
574 }
575
576 #[test]
577 fn extract_claims() {
578 let now = chrono::Utc
579 .with_ymd_and_hms(2018, 1, 18, 1, 30, 22)
580 .unwrap();
581 let expiration = now + chrono::Duration::microseconds(5 * 60 * 1000 * 1000);
582 let time_options = TimeOptions::new(now).leeway(chrono::Duration::zero());
583
584 let claims = serde_json::json!({
585 "iss": "https://foo.com",
586 "sub": "johndoe",
587 "aud": ["abcd-efgh"],
588 "iat": 1_516_239_022,
589 "nbf": 1_516_239_022,
590 "exp": 1_516_239_322,
591 "jti": "1122-3344-5566-7788",
592 });
593 let mut claims = serde_json::from_value(claims).unwrap();
594
595 let iss = ISS
596 .extract_required_with_options(&mut claims, "https://foo.com")
597 .unwrap();
598 let sub = SUB.extract_optional(&mut claims).unwrap();
599 let aud = AUD
600 .extract_optional_with_options(&mut claims, &"abcd-efgh".to_owned())
601 .unwrap();
602 let nbf = NBF
603 .extract_optional_with_options(&mut claims, &time_options)
604 .unwrap();
605 let exp = EXP
606 .extract_optional_with_options(&mut claims, &time_options)
607 .unwrap();
608 let iat = IAT
609 .extract_optional_with_options(&mut claims, &time_options)
610 .unwrap();
611 let jti = JTI.extract_optional(&mut claims).unwrap();
612
613 assert_eq!(iss, "https://foo.com".to_owned());
614 assert_eq!(sub, Some("johndoe".to_owned()));
615 assert_eq!(aud.as_deref(), Some(&vec!["abcd-efgh".to_owned()]));
616 assert_eq!(iat.as_deref(), Some(&now));
617 assert_eq!(nbf.as_deref(), Some(&now));
618 assert_eq!(exp.as_deref(), Some(&expiration));
619 assert_eq!(jti, Some("1122-3344-5566-7788".to_owned()));
620
621 assert!(claims.is_empty());
622 }
623
624 #[test]
625 fn time_validation() {
626 let now = chrono::Utc
627 .with_ymd_and_hms(2018, 1, 18, 1, 30, 22)
628 .unwrap();
629
630 let claims = serde_json::json!({
631 "iat": 1_516_239_022,
632 "nbf": 1_516_239_022,
633 "exp": 1_516_239_322,
634 });
635 let claims: HashMap<String, serde_json::Value> = serde_json::from_value(claims).unwrap();
636
637 {
639 let mut claims = claims.clone();
640
641 let time_options = TimeOptions::new(now).leeway(chrono::Duration::zero());
643 assert!(
644 IAT.extract_required_with_options(&mut claims, &time_options)
645 .is_ok()
646 );
647 assert!(
648 NBF.extract_required_with_options(&mut claims, &time_options)
649 .is_ok()
650 );
651 assert!(
652 EXP.extract_required_with_options(&mut claims, &time_options)
653 .is_ok()
654 );
655 }
656
657 let now = now - chrono::Duration::microseconds(60 * 1000 * 1000);
659
660 {
661 let mut claims = claims.clone();
663
664 let time_options = TimeOptions::new(now).leeway(chrono::Duration::zero());
666 assert!(matches!(
667 IAT.extract_required_with_options(&mut claims, &time_options),
668 Err(ClaimError::ValidationError { claim: "iat", .. }),
669 ));
670 assert!(matches!(
671 NBF.extract_required_with_options(&mut claims, &time_options),
672 Err(ClaimError::ValidationError { claim: "nbf", .. }),
673 ));
674 assert!(
675 EXP.extract_required_with_options(&mut claims, &time_options)
676 .is_ok()
677 );
678 }
679
680 {
681 let mut claims = claims.clone();
683
684 let time_options =
686 TimeOptions::new(now).leeway(chrono::Duration::microseconds(2 * 60 * 1000 * 1000));
687 assert!(
688 IAT.extract_required_with_options(&mut claims, &time_options)
689 .is_ok()
690 );
691 assert!(
692 NBF.extract_required_with_options(&mut claims, &time_options)
693 .is_ok()
694 );
695 assert!(
696 EXP.extract_required_with_options(&mut claims, &time_options)
697 .is_ok()
698 );
699 }
700
701 let now = now + chrono::Duration::microseconds((1 + 6) * 60 * 1000 * 1000);
703
704 {
705 let mut claims = claims.clone();
707
708 let time_options = TimeOptions::new(now).leeway(chrono::Duration::zero());
710 assert!(
711 IAT.extract_required_with_options(&mut claims, &time_options)
712 .is_ok()
713 );
714 assert!(
715 NBF.extract_required_with_options(&mut claims, &time_options)
716 .is_ok()
717 );
718 assert!(matches!(
719 EXP.extract_required_with_options(&mut claims, &time_options),
720 Err(ClaimError::ValidationError { claim: "exp", .. }),
721 ));
722 }
723
724 {
725 let mut claims = claims;
726
727 let time_options =
729 TimeOptions::new(now).leeway(chrono::Duration::try_minutes(2).unwrap());
730 assert!(
731 IAT.extract_required_with_options(&mut claims, &time_options)
732 .is_ok()
733 );
734 assert!(
735 NBF.extract_required_with_options(&mut claims, &time_options)
736 .is_ok()
737 );
738 assert!(
739 EXP.extract_required_with_options(&mut claims, &time_options)
740 .is_ok()
741 );
742 }
743 }
744
745 #[test]
746 fn invalid_claims() {
747 let now = chrono::Utc
748 .with_ymd_and_hms(2018, 1, 18, 1, 30, 22)
749 .unwrap();
750 let time_options = TimeOptions::new(now).leeway(chrono::Duration::zero());
751
752 let claims = serde_json::json!({
753 "iss": 123,
754 "sub": 456,
755 "aud": 789,
756 "iat": "123",
757 "nbf": "456",
758 "exp": "789",
759 "jti": 123,
760 });
761 let mut claims = serde_json::from_value(claims).unwrap();
762
763 assert!(matches!(
764 ISS.extract_required_with_options(&mut claims, "https://foo.com"),
765 Err(ClaimError::InvalidClaim("iss"))
766 ));
767 assert!(matches!(
768 SUB.extract_required(&mut claims),
769 Err(ClaimError::InvalidClaim("sub"))
770 ));
771 assert!(matches!(
772 AUD.extract_required_with_options(&mut claims, &"abcd-efgh".to_owned()),
773 Err(ClaimError::InvalidClaim("aud"))
774 ));
775 assert!(matches!(
776 NBF.extract_required_with_options(&mut claims, &time_options),
777 Err(ClaimError::InvalidClaim("nbf"))
778 ));
779 assert!(matches!(
780 EXP.extract_required_with_options(&mut claims, &time_options),
781 Err(ClaimError::InvalidClaim("exp"))
782 ));
783 assert!(matches!(
784 IAT.extract_required_with_options(&mut claims, &time_options),
785 Err(ClaimError::InvalidClaim("iat"))
786 ));
787 assert!(matches!(
788 JTI.extract_required(&mut claims),
789 Err(ClaimError::InvalidClaim("jti"))
790 ));
791 }
792
793 #[test]
794 fn missing_claims() {
795 let mut claims = HashMap::new();
797
798 assert!(matches!(
799 ISS.extract_required_with_options(&mut claims, "https://foo.com"),
800 Err(ClaimError::MissingClaim("iss"))
801 ));
802 assert!(matches!(
803 SUB.extract_required(&mut claims),
804 Err(ClaimError::MissingClaim("sub"))
805 ));
806 assert!(matches!(
807 AUD.extract_required_with_options(&mut claims, &"abcd-efgh".to_owned()),
808 Err(ClaimError::MissingClaim("aud"))
809 ));
810
811 assert!(matches!(
812 ISS.extract_optional_with_options(&mut claims, "https://foo.com"),
813 Ok(None)
814 ));
815 assert!(matches!(SUB.extract_optional(&mut claims), Ok(None)));
816 assert!(matches!(
817 AUD.extract_optional_with_options(&mut claims, &"abcd-efgh".to_owned()),
818 Ok(None)
819 ));
820 }
821
822 #[test]
823 fn string_eq_validation() {
824 let claims = serde_json::json!({
825 "iss": "https://foo.com",
826 });
827 let mut claims: HashMap<String, serde_json::Value> =
828 serde_json::from_value(claims).unwrap();
829
830 ISS.extract_required_with_options(&mut claims.clone(), "https://foo.com")
831 .unwrap();
832
833 assert!(matches!(
834 ISS.extract_required_with_options(&mut claims, "https://bar.com"),
835 Err(ClaimError::ValidationError { claim: "iss", .. }),
836 ));
837 }
838
839 #[test]
840 fn contains_validation() {
841 let claims = serde_json::json!({
842 "aud": "abcd-efgh",
843 });
844 let mut claims: HashMap<String, serde_json::Value> =
845 serde_json::from_value(claims).unwrap();
846
847 AUD.extract_required_with_options(&mut claims.clone(), &"abcd-efgh".to_owned())
848 .unwrap();
849
850 assert!(matches!(
851 AUD.extract_required_with_options(&mut claims, &"wxyz".to_owned()),
852 Err(ClaimError::ValidationError { claim: "aud", .. }),
853 ));
854 }
855}