common_access_token/claims.rs
1//! # Claims for Common Access Token
2//!
3//! This module provides the claims structure and related types for Common Access Tokens.
4//!
5//! Claims in Common Access Tokens are divided into two categories:
6//!
7//! - **Registered Claims**: Standard claims defined in RFC 8392, such as issuer, subject, audience, and expiration time.
8//! - **Custom Claims**: Application-specific claims that can contain any CBOR-encodable value.
9//!
10//! Claims are used to convey information about the token subject and context, and can be used for
11//! authorization decisions by the token verifier.
12
13use crate::header::CborValue;
14use std::collections::BTreeMap;
15
16/// CWT claim keys as defined in RFC 8392
17pub mod keys {
18 use crate::constants::cwt_keys;
19
20 /// Issuer claim key
21 pub const ISS: i32 = cwt_keys::ISS;
22 /// Subject claim key
23 pub const SUB: i32 = cwt_keys::SUB;
24 /// Audience claim key
25 pub const AUD: i32 = cwt_keys::AUD;
26 /// Expiration time claim key
27 pub const EXP: i32 = cwt_keys::EXP;
28 /// Not before claim key
29 pub const NBF: i32 = cwt_keys::NBF;
30 /// Issued at claim key
31 pub const IAT: i32 = cwt_keys::IAT;
32 /// CWT ID claim key
33 pub const CTI: i32 = cwt_keys::CTI;
34}
35
36/// Type alias for claims map
37pub type ClaimsMap = BTreeMap<i32, CborValue>;
38
39/// Standard registered claims as defined in RFC 8392.
40///
41/// These claims are standardized and have well-defined meanings:
42///
43/// - **iss** (Issuer): Identifies the principal that issued the token.
44/// - **sub** (Subject): Identifies the principal that is the subject of the token.
45/// - **aud** (Audience): Identifies the recipients that the token is intended for.
46/// - **exp** (Expiration Time): Identifies the expiration time on or after which the token MUST NOT be accepted.
47/// - **nbf** (Not Before): Identifies the time before which the token MUST NOT be accepted.
48/// - **iat** (Issued At): Identifies the time at which the token was issued.
49/// - **cti** (CWT ID): Provides a unique identifier for the token.
50///
51/// # Example
52///
53/// ```
54/// use common_access_token::RegisteredClaims;
55/// use common_access_token::current_timestamp;
56///
57/// let now = current_timestamp();
58/// let claims = RegisteredClaims::new()
59/// .with_issuer("example-issuer")
60/// .with_subject("user-123")
61/// .with_audience("example-service")
62/// .with_expiration(now + 3600) // 1 hour from now
63/// .with_not_before(now)
64/// .with_issued_at(now);
65///
66/// assert_eq!(claims.iss, Some("example-issuer".to_string()));
67/// assert_eq!(claims.sub, Some("user-123".to_string()));
68/// assert_eq!(claims.exp.unwrap(), now + 3600);
69/// ```
70#[derive(Debug, Clone, Default)]
71pub struct RegisteredClaims {
72 /// Issuer - identifies the principal that issued the token
73 pub iss: Option<String>,
74 /// Subject - identifies the principal that is the subject of the token
75 pub sub: Option<String>,
76 /// Audience - identifies the recipients that the token is intended for
77 pub aud: Option<String>,
78 /// Expiration time (seconds since Unix epoch) - token must not be accepted after this time
79 pub exp: Option<u64>,
80 /// Not before (seconds since Unix epoch) - token must not be accepted before this time
81 pub nbf: Option<u64>,
82 /// Issued at (seconds since Unix epoch) - when the token was issued
83 pub iat: Option<u64>,
84 /// CWT ID - unique identifier for the token
85 pub cti: Option<Vec<u8>>,
86}
87
88impl RegisteredClaims {
89 /// Creates a new empty set of registered claims.
90 ///
91 /// # Example
92 ///
93 /// ```
94 /// use common_access_token::RegisteredClaims;
95 ///
96 /// let claims = RegisteredClaims::new();
97 /// assert!(claims.iss.is_none());
98 /// assert!(claims.sub.is_none());
99 /// assert!(claims.exp.is_none());
100 /// ```
101 pub fn new() -> Self {
102 Self::default()
103 }
104
105 /// Sets the issuer claim.
106 ///
107 /// The issuer claim identifies the principal that issued the token.
108 ///
109 /// # Example
110 ///
111 /// ```
112 /// use common_access_token::RegisteredClaims;
113 ///
114 /// let claims = RegisteredClaims::new().with_issuer("https://auth.example.com");
115 /// assert_eq!(claims.iss, Some("https://auth.example.com".to_string()));
116 /// ```
117 pub fn with_issuer<S: Into<String>>(mut self, iss: S) -> Self {
118 self.iss = Some(iss.into());
119 self
120 }
121
122 /// Sets the subject claim.
123 ///
124 /// The subject claim identifies the principal that is the subject of the token.
125 ///
126 /// # Example
127 ///
128 /// ```
129 /// use common_access_token::RegisteredClaims;
130 ///
131 /// let claims = RegisteredClaims::new().with_subject("user-123");
132 /// assert_eq!(claims.sub, Some("user-123".to_string()));
133 /// ```
134 pub fn with_subject<S: Into<String>>(mut self, sub: S) -> Self {
135 self.sub = Some(sub.into());
136 self
137 }
138
139 /// Sets the audience claim.
140 ///
141 /// The audience claim identifies the recipients that the token is intended for.
142 ///
143 /// # Example
144 ///
145 /// ```
146 /// use common_access_token::RegisteredClaims;
147 ///
148 /// let claims = RegisteredClaims::new().with_audience("https://api.example.com");
149 /// assert_eq!(claims.aud, Some("https://api.example.com".to_string()));
150 /// ```
151 pub fn with_audience<S: Into<String>>(mut self, aud: S) -> Self {
152 self.aud = Some(aud.into());
153 self
154 }
155
156 /// Sets the expiration time claim.
157 ///
158 /// The expiration time claim identifies the time on or after which the token
159 /// MUST NOT be accepted for processing. The value is a Unix timestamp (seconds
160 /// since the Unix epoch).
161 ///
162 /// # Example
163 ///
164 /// ```
165 /// use common_access_token::RegisteredClaims;
166 /// use common_access_token::current_timestamp;
167 ///
168 /// let now = current_timestamp();
169 /// let claims = RegisteredClaims::new().with_expiration(now + 3600); // 1 hour from now
170 /// assert_eq!(claims.exp, Some(now + 3600));
171 /// ```
172 pub fn with_expiration(mut self, exp: u64) -> Self {
173 self.exp = Some(exp);
174 self
175 }
176
177 /// Sets the not before claim.
178 ///
179 /// The not before claim identifies the time before which the token
180 /// MUST NOT be accepted for processing. The value is a Unix timestamp (seconds
181 /// since the Unix epoch).
182 ///
183 /// # Example
184 ///
185 /// ```
186 /// use common_access_token::RegisteredClaims;
187 /// use common_access_token::current_timestamp;
188 ///
189 /// let now = current_timestamp();
190 /// let claims = RegisteredClaims::new().with_not_before(now); // Valid from now
191 /// assert_eq!(claims.nbf, Some(now));
192 /// ```
193 pub fn with_not_before(mut self, nbf: u64) -> Self {
194 self.nbf = Some(nbf);
195 self
196 }
197
198 /// Sets the issued at claim.
199 ///
200 /// The issued at claim identifies the time at which the token was issued.
201 /// The value is a Unix timestamp (seconds since the Unix epoch).
202 ///
203 /// # Example
204 ///
205 /// ```
206 /// use common_access_token::RegisteredClaims;
207 /// use common_access_token::current_timestamp;
208 ///
209 /// let now = current_timestamp();
210 /// let claims = RegisteredClaims::new().with_issued_at(now);
211 /// assert_eq!(claims.iat, Some(now));
212 /// ```
213 pub fn with_issued_at(mut self, iat: u64) -> Self {
214 self.iat = Some(iat);
215 self
216 }
217
218 /// Sets the CWT ID claim.
219 ///
220 /// The CWT ID claim provides a unique identifier for the token.
221 ///
222 /// # Example
223 ///
224 /// ```
225 /// use common_access_token::RegisteredClaims;
226 ///
227 /// let id = vec![1, 2, 3, 4];
228 /// let claims = RegisteredClaims::new().with_cti(id.clone());
229 /// assert_eq!(claims.cti, Some(id));
230 /// ```
231 pub fn with_cti<T: Into<Vec<u8>>>(mut self, cti: T) -> Self {
232 self.cti = Some(cti.into());
233 self
234 }
235
236 /// Set token lifetime with issued-at, not-before, and expiration claims
237 ///
238 /// This is a convenience method that sets all three time-related claims:
239 /// - `iat` (issued at) is set to the current time
240 /// - `nbf` (not before) is set to the current time
241 /// - `exp` (expiration) is set to current time plus the specified seconds
242 ///
243 /// # Example
244 ///
245 /// ```
246 /// use common_access_token::{RegisteredClaims, current_timestamp};
247 ///
248 /// // Token valid for 1 hour
249 /// let claims = RegisteredClaims::new().with_lifetime_secs(3600);
250 ///
251 /// let now = current_timestamp();
252 /// assert_eq!(claims.iat, Some(now));
253 /// assert_eq!(claims.nbf, Some(now));
254 /// assert_eq!(claims.exp, Some(now + 3600));
255 /// ```
256 pub fn with_lifetime_secs(mut self, seconds: u64) -> Self {
257 let now = crate::utils::current_timestamp();
258 self.iat = Some(now);
259 self.nbf = Some(now);
260 self.exp = Some(now + seconds);
261 self
262 }
263
264 /// Set token lifetime with issued-at, not-before, and expiration claims using a Duration
265 ///
266 /// This is a convenience method that sets all three time-related claims:
267 /// - `iat` (issued at) is set to the current time
268 /// - `nbf` (not before) is set to the current time
269 /// - `exp` (expiration) is set to current time plus the specified duration
270 ///
271 /// # Example
272 ///
273 /// ```
274 /// use common_access_token::{RegisteredClaims, current_timestamp};
275 /// use std::time::Duration;
276 ///
277 /// // Token valid for 1 hour
278 /// let claims = RegisteredClaims::new().with_lifetime(Duration::from_secs(3600));
279 ///
280 /// let now = current_timestamp();
281 /// assert_eq!(claims.iat, Some(now));
282 /// assert_eq!(claims.nbf, Some(now));
283 /// assert_eq!(claims.exp, Some(now + 3600));
284 /// ```
285 pub fn with_lifetime(self, duration: std::time::Duration) -> Self {
286 self.with_lifetime_secs(duration.as_secs())
287 }
288
289 /// Check if the claims have expired
290 ///
291 /// Returns `true` if there's an expiration claim and the current time is at or after it.
292 /// Returns `false` if there's no expiration claim or it hasn't expired yet.
293 ///
294 /// # Example
295 ///
296 /// ```
297 /// use common_access_token::{RegisteredClaims, current_timestamp};
298 ///
299 /// let now = current_timestamp();
300 /// let claims = RegisteredClaims::new().with_expiration(now + 3600);
301 /// assert!(!claims.is_expired());
302 ///
303 /// let expired = RegisteredClaims::new().with_expiration(now - 100);
304 /// assert!(expired.is_expired());
305 /// ```
306 pub fn is_expired(&self) -> bool {
307 if let Some(exp) = self.exp {
308 crate::utils::current_timestamp() >= exp
309 } else {
310 false
311 }
312 }
313
314 /// Check if the claims are valid based on the not-before claim
315 ///
316 /// Returns `true` if there's no nbf claim or if the current time is at or after it.
317 /// Returns `false` if there's an nbf claim and the current time is before it.
318 ///
319 /// # Example
320 ///
321 /// ```
322 /// use common_access_token::{RegisteredClaims, current_timestamp};
323 ///
324 /// let now = current_timestamp();
325 /// let claims = RegisteredClaims::new().with_not_before(now);
326 /// assert!(claims.is_valid_yet());
327 ///
328 /// let future = RegisteredClaims::new().with_not_before(now + 3600);
329 /// assert!(!future.is_valid_yet());
330 /// ```
331 pub fn is_valid_yet(&self) -> bool {
332 if let Some(nbf) = self.nbf {
333 crate::utils::current_timestamp() >= nbf
334 } else {
335 true
336 }
337 }
338
339 /// Converts registered claims to a claims map.
340 ///
341 /// This method is primarily used internally for token encoding.
342 ///
343 /// # Example
344 ///
345 /// ```
346 /// use common_access_token::RegisteredClaims;
347 /// use common_access_token::header::CborValue;
348 /// use common_access_token::claims::keys;
349 ///
350 /// let claims = RegisteredClaims::new()
351 /// .with_issuer("example-issuer")
352 /// .with_subject("user-123");
353 ///
354 /// let map = claims.to_map();
355 /// assert!(matches!(map.get(&keys::ISS), Some(CborValue::Text(s)) if s == "example-issuer"));
356 /// assert!(matches!(map.get(&keys::SUB), Some(CborValue::Text(s)) if s == "user-123"));
357 /// ```
358 pub fn to_map(&self) -> ClaimsMap {
359 let mut map = ClaimsMap::new();
360
361 if let Some(iss) = &self.iss {
362 map.insert(keys::ISS, CborValue::Text(iss.clone()));
363 }
364
365 if let Some(sub) = &self.sub {
366 map.insert(keys::SUB, CborValue::Text(sub.clone()));
367 }
368
369 if let Some(aud) = &self.aud {
370 map.insert(keys::AUD, CborValue::Text(aud.clone()));
371 }
372
373 if let Some(exp) = self.exp {
374 map.insert(keys::EXP, CborValue::Integer(exp as i64));
375 }
376
377 if let Some(nbf) = self.nbf {
378 map.insert(keys::NBF, CborValue::Integer(nbf as i64));
379 }
380
381 if let Some(iat) = self.iat {
382 map.insert(keys::IAT, CborValue::Integer(iat as i64));
383 }
384
385 if let Some(cti) = &self.cti {
386 map.insert(keys::CTI, CborValue::Bytes(cti.clone()));
387 }
388
389 map
390 }
391
392 /// Extracts registered claims from a claims map.
393 ///
394 /// This method is primarily used internally for token decoding.
395 ///
396 /// # Example
397 ///
398 /// ```
399 /// use common_access_token::{RegisteredClaims, CborValue};
400 /// use common_access_token::claims::{keys, ClaimsMap};
401 ///
402 /// let mut map = ClaimsMap::new();
403 /// map.insert(keys::ISS, CborValue::Text("example-issuer".to_string()));
404 /// map.insert(keys::SUB, CborValue::Text("user-123".to_string()));
405 ///
406 /// let claims = RegisteredClaims::from_map(&map);
407 /// assert_eq!(claims.iss, Some("example-issuer".to_string()));
408 /// assert_eq!(claims.sub, Some("user-123".to_string()));
409 /// ```
410 pub fn from_map(map: &ClaimsMap) -> Self {
411 let mut claims = Self::new();
412
413 if let Some(CborValue::Text(iss)) = map.get(&keys::ISS) {
414 claims.iss = Some(iss.clone());
415 }
416
417 if let Some(CborValue::Text(sub)) = map.get(&keys::SUB) {
418 claims.sub = Some(sub.clone());
419 }
420
421 if let Some(CborValue::Text(aud)) = map.get(&keys::AUD) {
422 claims.aud = Some(aud.clone());
423 }
424
425 if let Some(CborValue::Integer(exp)) = map.get(&keys::EXP) {
426 claims.exp = Some(*exp as u64);
427 }
428
429 if let Some(CborValue::Integer(nbf)) = map.get(&keys::NBF) {
430 claims.nbf = Some(*nbf as u64);
431 }
432
433 if let Some(CborValue::Integer(iat)) = map.get(&keys::IAT) {
434 claims.iat = Some(*iat as u64);
435 }
436
437 if let Some(CborValue::Bytes(cti)) = map.get(&keys::CTI) {
438 claims.cti = Some(cti.clone());
439 }
440
441 claims
442 }
443}
444
445/// Claims for a Common Access Token.
446///
447/// This struct combines standard registered claims with custom application-specific claims.
448/// It provides a flexible way to include both standardized information and custom data
449/// in a token.
450///
451/// # Example
452///
453/// ```
454/// use common_access_token::{Claims, RegisteredClaims};
455/// use common_access_token::current_timestamp;
456///
457/// let now = current_timestamp();
458/// let registered_claims = RegisteredClaims::new()
459/// .with_issuer("example-issuer")
460/// .with_expiration(now + 3600);
461///
462/// let claims = Claims::new()
463/// .with_registered_claims(registered_claims)
464/// .with_custom_string(100, "custom-value")
465/// .with_custom_int(101, 42);
466///
467/// // Access claims
468/// assert_eq!(claims.registered.iss, Some("example-issuer".to_string()));
469/// ```
470#[derive(Debug, Clone, Default)]
471pub struct Claims {
472 /// Standard registered claims as defined in RFC 8392
473 pub registered: RegisteredClaims,
474 /// Custom application-specific claims with integer keys
475 pub custom: ClaimsMap,
476}
477
478impl Claims {
479 /// Creates a new empty claims set with no registered or custom claims.
480 ///
481 /// # Example
482 ///
483 /// ```
484 /// use common_access_token::Claims;
485 ///
486 /// let claims = Claims::new();
487 /// assert!(claims.registered.iss.is_none());
488 /// assert!(claims.custom.is_empty());
489 /// ```
490 pub fn new() -> Self {
491 Self::default()
492 }
493
494 /// Sets the registered claims.
495 ///
496 /// This method replaces any existing registered claims with the provided ones.
497 ///
498 /// # Example
499 ///
500 /// ```
501 /// use common_access_token::{Claims, RegisteredClaims};
502 ///
503 /// let registered = RegisteredClaims::new()
504 /// .with_issuer("example-issuer")
505 /// .with_subject("user-123");
506 ///
507 /// let claims = Claims::new().with_registered_claims(registered);
508 /// assert_eq!(claims.registered.iss, Some("example-issuer".to_string()));
509 /// ```
510 pub fn with_registered_claims(mut self, registered: RegisteredClaims) -> Self {
511 self.registered = registered;
512 self
513 }
514
515 /// Adds a custom claim with a string value.
516 ///
517 /// # Example
518 ///
519 /// ```
520 /// use common_access_token::Claims;
521 /// use common_access_token::header::CborValue;
522 ///
523 /// let claims = Claims::new().with_custom_string(100, "custom-value");
524 /// assert!(matches!(claims.custom.get(&100), Some(CborValue::Text(s)) if s == "custom-value"));
525 /// ```
526 pub fn with_custom_string<S: Into<String>>(mut self, key: i32, value: S) -> Self {
527 self.custom.insert(key, CborValue::Text(value.into()));
528 self
529 }
530
531 /// Adds a custom claim with a binary value.
532 ///
533 /// # Example
534 ///
535 /// ```
536 /// use common_access_token::Claims;
537 /// use common_access_token::header::CborValue;
538 ///
539 /// let binary_data = vec![0x01, 0x02, 0x03];
540 /// let claims = Claims::new().with_custom_binary(101, binary_data.clone());
541 /// assert!(matches!(claims.custom.get(&101), Some(CborValue::Bytes(b)) if b == &binary_data));
542 /// ```
543 pub fn with_custom_binary<B: Into<Vec<u8>>>(mut self, key: i32, value: B) -> Self {
544 self.custom.insert(key, CborValue::Bytes(value.into()));
545 self
546 }
547
548 /// Adds a custom claim with an integer value.
549 ///
550 /// # Example
551 ///
552 /// ```
553 /// use common_access_token::Claims;
554 /// use common_access_token::header::CborValue;
555 ///
556 /// let claims = Claims::new().with_custom_int(102, 42);
557 /// assert!(matches!(claims.custom.get(&102), Some(CborValue::Integer(i)) if *i == 42));
558 /// ```
559 pub fn with_custom_int(mut self, key: i32, value: i64) -> Self {
560 self.custom.insert(key, CborValue::Integer(value));
561 self
562 }
563
564 /// Adds a custom claim with a nested map value.
565 ///
566 /// This allows for complex structured data to be included in the token.
567 ///
568 /// # Example
569 ///
570 /// ```
571 /// use common_access_token::{Claims, CborValue};
572 /// use std::collections::BTreeMap;
573 ///
574 /// let mut nested_map = BTreeMap::new();
575 /// nested_map.insert(1, CborValue::Text("nested-value".to_string()));
576 ///
577 /// let claims = Claims::new().with_custom_map(103, nested_map);
578 /// if let Some(CborValue::Map(map)) = claims.custom.get(&103) {
579 /// if let Some(CborValue::Text(value)) = map.get(&1) {
580 /// assert_eq!(value, "nested-value");
581 /// }
582 /// }
583 /// ```
584 pub fn with_custom_map(mut self, key: i32, value: BTreeMap<i32, CborValue>) -> Self {
585 self.custom.insert(key, CborValue::Map(value));
586 self
587 }
588
589 /// Get a custom claim as a string
590 ///
591 /// Returns `Some(&str)` if the claim exists and is a text value, `None` otherwise.
592 ///
593 /// # Example
594 ///
595 /// ```
596 /// use common_access_token::Claims;
597 ///
598 /// let claims = Claims::new().with_custom_string(100, "custom-value");
599 /// assert_eq!(claims.get_custom_string(100), Some("custom-value"));
600 /// assert_eq!(claims.get_custom_string(999), None);
601 /// ```
602 pub fn get_custom_string(&self, key: i32) -> Option<&str> {
603 match self.custom.get(&key) {
604 Some(CborValue::Text(s)) => Some(s.as_str()),
605 _ => None,
606 }
607 }
608
609 /// Get a custom claim as an integer
610 ///
611 /// Returns `Some(i64)` if the claim exists and is an integer value, `None` otherwise.
612 ///
613 /// # Example
614 ///
615 /// ```
616 /// use common_access_token::Claims;
617 ///
618 /// let claims = Claims::new().with_custom_int(100, 42);
619 /// assert_eq!(claims.get_custom_int(100), Some(42));
620 /// assert_eq!(claims.get_custom_int(999), None);
621 /// ```
622 pub fn get_custom_int(&self, key: i32) -> Option<i64> {
623 match self.custom.get(&key) {
624 Some(CborValue::Integer(i)) => Some(*i),
625 _ => None,
626 }
627 }
628
629 /// Get a custom claim as binary data
630 ///
631 /// Returns `Some(&[u8])` if the claim exists and is a bytes value, `None` otherwise.
632 ///
633 /// # Example
634 ///
635 /// ```
636 /// use common_access_token::Claims;
637 ///
638 /// let data = vec![1, 2, 3, 4];
639 /// let claims = Claims::new().with_custom_binary(100, data.clone());
640 /// assert_eq!(claims.get_custom_binary(100), Some(data.as_slice()));
641 /// assert_eq!(claims.get_custom_binary(999), None);
642 /// ```
643 pub fn get_custom_binary(&self, key: i32) -> Option<&[u8]> {
644 match self.custom.get(&key) {
645 Some(CborValue::Bytes(b)) => Some(b.as_slice()),
646 _ => None,
647 }
648 }
649
650 /// Get a reference to a custom claim value
651 ///
652 /// Returns `Some(&CborValue)` if the claim exists, `None` otherwise.
653 ///
654 /// # Example
655 ///
656 /// ```
657 /// use common_access_token::{Claims, CborValue};
658 ///
659 /// let claims = Claims::new().with_custom_string(100, "value");
660 /// if let Some(CborValue::Text(s)) = claims.get_custom_claim(100) {
661 /// assert_eq!(s, "value");
662 /// }
663 /// ```
664 pub fn get_custom_claim(&self, key: i32) -> Option<&CborValue> {
665 self.custom.get(&key)
666 }
667
668 /// Check if a custom claim exists
669 ///
670 /// # Example
671 ///
672 /// ```
673 /// use common_access_token::Claims;
674 ///
675 /// let claims = Claims::new().with_custom_string(100, "value");
676 /// assert!(claims.has_custom_claim(100));
677 /// assert!(!claims.has_custom_claim(999));
678 /// ```
679 pub fn has_custom_claim(&self, key: i32) -> bool {
680 self.custom.contains_key(&key)
681 }
682
683 /// Converts all claims (registered and custom) to a combined claims map.
684 ///
685 /// This method is primarily used internally for token encoding.
686 ///
687 /// # Example
688 ///
689 /// ```
690 /// use common_access_token::{Claims, RegisteredClaims};
691 ///
692 /// let claims = Claims::new()
693 /// .with_registered_claims(RegisteredClaims::new().with_issuer("example-issuer"))
694 /// .with_custom_string(100, "custom-value");
695 ///
696 /// let map = claims.to_map();
697 /// assert_eq!(map.len(), 2); // One registered claim + one custom claim
698 /// ```
699 pub fn to_map(&self) -> ClaimsMap {
700 let mut map = self.registered.to_map();
701
702 // Add custom claims
703 for (key, value) in &self.custom {
704 map.insert(*key, value.clone());
705 }
706
707 map
708 }
709
710 /// Creates a Claims struct from a claims map.
711 ///
712 /// This method is primarily used internally for token decoding.
713 ///
714 /// # Example
715 ///
716 /// ```
717 /// use common_access_token::{Claims, CborValue};
718 /// use common_access_token::claims::{keys, ClaimsMap};
719 ///
720 /// let mut map = ClaimsMap::new();
721 /// map.insert(keys::ISS, CborValue::Text("example-issuer".to_string()));
722 /// map.insert(100, CborValue::Text("custom-value".to_string()));
723 ///
724 /// let claims = Claims::from_map(&map);
725 /// assert_eq!(claims.registered.iss, Some("example-issuer".to_string()));
726 /// assert!(claims.custom.contains_key(&100));
727 /// ```
728 pub fn from_map(map: &ClaimsMap) -> Self {
729 let registered = RegisteredClaims::from_map(map);
730
731 // Extract custom claims (all keys not in the registered claims)
732 let mut custom = ClaimsMap::new();
733 for (key, value) in map {
734 if !matches!(
735 *key,
736 keys::ISS | keys::SUB | keys::AUD | keys::EXP | keys::NBF | keys::IAT | keys::CTI
737 ) {
738 custom.insert(*key, value.clone());
739 }
740 }
741
742 Self { registered, custom }
743 }
744}