Skip to main content

rjwt/
lib.rs

1//! Provides an [`Actor`] and (de)serializable [`Token`] struct which support authenticating
2//! JSON Web Tokens with a custom payload. See [jwt.io](http://jwt.io) for more information
3//! on the JWT spec.
4//!
5//! The provided [`Actor`] uses the
6//! [ECDSA](https://en.wikipedia.org/wiki/Elliptic_Curve_Digital_Signature_Algorithm)
7//! algorithm to sign tokens (using the [`ed25519_dalek`] crate).
8//!
9//! This library differs from other JWT implementations in that it allows for recursive [`Token`]s.
10//!
11//! Note that if the same `(host, actor)` pair is specified multiple times in the token chain,
12//! only the latest is returned by [`Claims::get`].
13//!
14//! Example:
15//! ```
16//! # use std::collections::HashMap;
17//! # use std::time::{Duration, SystemTime};
18//! # use async_trait::async_trait;
19//! # use futures::executor::block_on;
20//! use rjwt::*;
21//!
22//! #[derive(Clone)]
23//! struct Resolver {
24//!     hostname: String,
25//!     actors: HashMap<String, Actor<String>>,
26//!     peers: Vec<Self>,
27//! }
28//! // ...
29//! # impl Resolver {
30//! #    fn new<A: IntoIterator<Item = Actor<String>>>(hostname: String, actors: A, peers: Vec<Self>) -> Self {
31//! #        Self { hostname, actors: actors.into_iter().map(|a| (a.id().clone(), a)).collect(), peers }
32//! #    }
33//! # }
34//!
35//! #[async_trait]
36//! impl Resolve for Resolver {
37//!     type HostId = String;
38//!     type ActorId = String;
39//!     type Claims = String;
40//!
41//!     async fn resolve(&self, host: &Self::HostId, actor_id: &Self::ActorId) -> Result<Actor<Self::ActorId>, Error> {
42//!         if host == &self.hostname {
43//!             self.actors.get(actor_id).cloned().ok_or_else(|| Error::fetch(actor_id))
44//!         } else if let Some(peer) = self.peers.iter().filter(|p| &p.hostname == host).next() {
45//!             peer.resolve(host, actor_id).await
46//!         } else {
47//!             Err(Error::fetch(host))
48//!         }
49//!     }
50//! }
51//!
52//! let now = SystemTime::now();
53//!
54//! // Say that Bob is a user on example.com.
55//! let bobs_id = "bob".to_string();
56//! let example_dot_com = "example.com".to_string();
57//!
58//! let actor_bob = Actor::new(bobs_id.clone());
59//! let example = Resolver::new(example_dot_com.clone(), [actor_bob.clone()], vec![]);
60//!
61//! // Bob makes a request through the retailer.com app.
62//! let retailer_dot_com = "retailer.com".to_string();
63//! let retail_app = Actor::new("app".to_string());
64//! let retailer = Resolver::new(
65//!     retailer_dot_com.clone(),
66//!     [retail_app.clone()],
67//!     vec![example.clone()]);
68//!
69//! // The retailer.com app makes a request to Bob's bank.
70//! let bank_account = Actor::new("bank".to_string());
71//! let bank = Resolver::new(
72//!     "bank.com".to_string(),
73//!     [bank_account.clone()],
74//!     vec![example, retailer.clone()]);
75//!
76//! // First, example.com issues a token to authenticate Bob.
77//! let bobs_claim = String::from("I am Bob and retailer.com may debit my bank.com account");
78//!
79//! // This requires constructing the token...
80//! let bobs_token = Token::new(
81//!     example_dot_com.clone(),
82//!     now,
83//!     Duration::from_secs(30),
84//!     actor_bob.id().to_string(),
85//!     bobs_claim.clone());
86//!
87//! // and signing it with Bob's private key.
88//! let bobs_token = actor_bob.sign_token(bobs_token).expect("signed token");
89//!
90//! // Then, retailer.com validates the token...
91//! let bobs_token = block_on(retailer.verify(bobs_token.into_jwt(), now)).expect("claims");
92//! assert!(bobs_token.claims().get(&example_dot_com, &bobs_id).expect("claim").starts_with("I am Bob"));
93//!
94//! // and adds its own claim, that Bob owes it $1.
95//! let retailer_claim = String::from("Bob spent $1 on retailer.com");
96//! let retailer_token = retail_app.consume_and_sign(
97//!     bobs_token,
98//!     retailer_dot_com.clone(),
99//!     retailer_claim.clone(),
100//!     now).expect("signed token");
101//!
102//! assert_eq!(retailer_token
103//!     .claims()
104//!     .get(&retailer_dot_com, retail_app.id()), Some(&retailer_claim));
105//!
106//! assert_eq!(retailer_token
107//!     .claims()
108//!     .get(&example_dot_com, actor_bob.id()), Some(&bobs_claim));
109//!
110//! // Finally, Bob's bank verifies the token...
111//! let retailer_token_as_received = block_on(
112//!     bank.verify(retailer_token.jwt().to_string(), now)
113//! ).expect("claims");
114//!
115//! assert_eq!(retailer_token, retailer_token_as_received);
116//!
117//! // to authenticate that the request came from Bob...
118//! assert!(retailer_token_as_received
119//!     .claims()
120//!     .get(&example_dot_com, &bobs_id)
121//!     .expect("claim")
122//!     .starts_with("I am Bob and retailer.com may debit my bank.com account"));
123//!
124//! // via retailer.com.
125//! assert!(retailer_token_as_received
126//!     .claims()
127//!     .get(&retailer_dot_com, retail_app.id())
128//!     .expect("claim")
129//!     .starts_with("Bob spent $1"));
130//! ```
131
132use std::fmt;
133use std::pin::Pin;
134use std::time::{Duration, SystemTime, SystemTimeError, UNIX_EPOCH};
135
136use async_trait::async_trait;
137use base64::prelude::*;
138use ed25519_dalek::{SignatureError, Signer, Verifier};
139use futures::Future;
140use serde::de::DeserializeOwned;
141use serde::{Deserialize, Serialize};
142
143pub use ed25519_dalek::{Signature, SigningKey, VerifyingKey};
144pub use rand::rngs::OsRng;
145
146/// The category of error returned by a JWT operation
147#[derive(Copy, Clone, Debug, Eq, PartialEq)]
148pub enum ErrorKind {
149    /// An authentication error
150    Auth,
151    Base64,
152    Fetch,
153    Format,
154    Json,
155    Time,
156}
157
158/// An error returned by a JWT operation
159#[derive(Debug)]
160pub struct Error {
161    kind: ErrorKind,
162    message: String,
163}
164
165impl Error {
166    /// Construct a new [`Error`].
167    pub fn new(kind: ErrorKind, message: String) -> Self {
168        Self { kind, message }
169    }
170
171    /// Return the [`ErrorKind`] of this [`Error`].
172    pub fn kind(&self) -> ErrorKind {
173        self.kind
174    }
175
176    /// Destructure this [`Error`] into its [`ErrorKind`] and an error message [`String`].
177    pub fn into_inner(self) -> (ErrorKind, String) {
178        (self.kind, self.message)
179    }
180
181    /// Construct a new authentication [`Error`].
182    pub fn auth<M: fmt::Display>(message: M) -> Self {
183        Self::new(ErrorKind::Auth, message.to_string())
184    }
185
186    /// Construct a new JWT format [`Error`].
187    pub fn format<M: fmt::Display>(cause: M) -> Self {
188        Self::new(ErrorKind::Format, cause.to_string())
189    }
190
191    /// Construct a new JWT actor retrieval [`Error`].
192    pub fn fetch<Info: fmt::Debug>(info: Info) -> Self {
193        Self::new(ErrorKind::Fetch, format!("{info:?}"))
194    }
195}
196
197impl fmt::Display for Error {
198    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
199        write!(f, "{:?}: {}", self.kind, self.message)
200    }
201}
202
203impl std::error::Error for Error {}
204
205impl From<base64::DecodeError> for Error {
206    fn from(cause: base64::DecodeError) -> Self {
207        Self::new(ErrorKind::Base64, cause.to_string())
208    }
209}
210
211impl From<serde_json::Error> for Error {
212    fn from(cause: serde_json::Error) -> Self {
213        Self::new(ErrorKind::Json, cause.to_string())
214    }
215}
216
217impl From<SignatureError> for Error {
218    fn from(cause: SignatureError) -> Self {
219        Self::new(ErrorKind::Auth, cause.to_string())
220    }
221}
222
223impl From<SystemTimeError> for Error {
224    fn from(cause: SystemTimeError) -> Self {
225        Self::new(ErrorKind::Time, cause.to_string())
226    }
227}
228
229/// Trait which defines how to fetch an [`Actor`] given its host and ID
230#[async_trait]
231pub trait Resolve: Send + Sync {
232    type HostId: Serialize + DeserializeOwned + fmt::Debug + Send + Sync;
233    type ActorId: Serialize + DeserializeOwned + fmt::Debug + Send + Sync;
234    type Claims: Serialize + DeserializeOwned + Send + Sync;
235
236    /// Given a host and actor ID, return a corresponding [`Actor`].
237    async fn resolve(
238        &self,
239        host: &Self::HostId,
240        actor_id: &Self::ActorId,
241    ) -> Result<Actor<Self::ActorId>, Error>;
242
243    /// Decode and verify the given `encoded` token.
244    async fn verify(
245        &self,
246        encoded: String,
247        now: SystemTime,
248    ) -> Result<SignedToken<Self::HostId, Self::ActorId, Self::Claims>, Error>
249    where
250        Self::ActorId: PartialEq,
251    {
252        let claims = verify_claims(self, &encoded, now).await?;
253        Ok(SignedToken::new(claims, encoded))
254    }
255}
256
257async fn decode_and_verify_token<R: Resolve + ?Sized>(
258    resolver: &R,
259    encoded: &str,
260    now: SystemTime,
261) -> Result<Token<R::HostId, R::ActorId, R::Claims>, Error>
262where
263    R::ActorId: PartialEq,
264{
265    let (message, signature) = token_signature(encoded)?;
266    let token: Token<R::HostId, R::ActorId, R::Claims> = decode_token(message)?;
267
268    if token.is_expired(now) {
269        return Err(Error::new(ErrorKind::Time, "token is expired".into()));
270    }
271
272    let actor = resolver.resolve(&token.iss, &token.actor_id).await?;
273
274    if actor.id != token.actor_id {
275        return Err(Error::auth(
276            "attempted to use a bearer token for a different actor",
277        ));
278    }
279
280    if let Err(cause) = actor.public_key().verify(message.as_bytes(), &signature) {
281        Err(Error::auth(format!("invalid bearer token: {cause}")))
282    } else {
283        Ok(token)
284    }
285}
286
287type Verification<'a, H, A, C> =
288    Pin<Box<dyn Future<Output = Result<Claims<H, A, C>, Error>> + Send + 'a>>;
289
290fn verify_claims<'a, R>(
291    resolver: &'a R,
292    encoded: &'a str,
293    now: SystemTime,
294) -> Verification<'a, R::HostId, R::ActorId, R::Claims>
295where
296    R: Resolve + ?Sized,
297    R::ActorId: PartialEq,
298{
299    Box::pin(async move {
300        let token = decode_and_verify_token(resolver, encoded, now).await?;
301
302        if let Some(parent) = token.inherit {
303            let parent_claims = verify_claims(resolver, &parent, now).await?;
304
305            if token.exp <= parent_claims.exp {
306                parent_claims.consume(token.iss, token.actor_id, token.custom)
307            } else {
308                Err(Error::new(
309                    ErrorKind::Time,
310                    "cannot extend the expiration time of a recursive token".into(),
311                ))
312            }
313        } else {
314            Ok(Claims::new(
315                token.exp,
316                token.iss,
317                token.actor_id,
318                token.custom,
319            ))
320        }
321    })
322}
323
324enum Key {
325    Public(VerifyingKey),
326    Private(SigningKey),
327}
328
329impl Key {
330    fn has_private_key(&self) -> bool {
331        match &self {
332            Self::Public(_) => false,
333            Self::Private(_) => true,
334        }
335    }
336}
337
338/// An actor with an identifier of type `T` and an ECDSA keypair used to sign tokens.
339///
340/// *IMPORTANT NOTE*: for security reasons, although `Actor` implements `Clone`, its secret key will
341/// NOT be cloned. For example:
342/// ```
343/// # use rjwt::Actor;
344/// let actor = Actor::<String>::new("id".to_string()); // this has a new secret key
345/// let cloned = actor.clone(); // this does NOT have a secret key, only a public key
346/// ```
347pub struct Actor<A> {
348    id: A,
349    key: Key,
350}
351
352impl<A> Actor<A> {
353    /// Return an `Actor` with a newly-generated keypair.
354    pub fn new(id: A) -> Self {
355        Self::with_keypair(id, SigningKey::generate(&mut OsRng))
356    }
357
358    /// Return an `Actor` with the given keypair, or an error if the keypair is invalid.
359    pub fn with_keypair(id: A, keypair: SigningKey) -> Self {
360        Self {
361            id,
362            key: Key::Private(keypair),
363        }
364    }
365
366    /// Return an `Actor` with the given public key, or an error if the key is invalid.
367    pub fn with_public_key(id: A, public_key: VerifyingKey) -> Self {
368        Self {
369            id,
370            key: Key::Public(public_key),
371        }
372    }
373
374    /// Borrow the identifier of this actor.
375    pub fn id(&self) -> &A {
376        &self.id
377    }
378
379    /// Return `true` if this [`Actor`] has a private key which can be used to sign [`Token`]s.
380    pub fn has_private_key(&self) -> bool {
381        self.key.has_private_key()
382    }
383
384    /// Borrow the public key of this actor, which a client can use to verify a signature.
385    pub fn public_key(&self) -> VerifyingKey {
386        match &self.key {
387            Key::Public(public_key) => *public_key,
388            Key::Private(keypair) => keypair.verifying_key(),
389        }
390    }
391
392    fn sign_token_inner<H, C>(&self, token: &Token<H, A, C>) -> Result<String, Error>
393    where
394        H: Serialize,
395        A: Serialize,
396        C: Serialize,
397    {
398        let keypair = match &self.key {
399            Key::Private(keypair) => Ok(keypair),
400            Key::Public(_) => Err(Error::auth("cannot sign a token without a private key")),
401        }?;
402
403        let header = BASE64_STANDARD.encode(serde_json::to_string(&TokenHeader::default())?);
404        let claims = BASE64_STANDARD.encode(serde_json::to_string(&token)?);
405
406        let signature = keypair.try_sign(format!("{header}.{claims}").as_bytes())?;
407        let signature = BASE64_STANDARD.encode(signature.to_bytes());
408
409        Ok(format!("{header}.{claims}.{signature}"))
410    }
411
412    /// Encode and sign the given `token` data.
413    pub fn sign_token<H, C>(&self, token: Token<H, A, C>) -> Result<SignedToken<H, A, C>, Error>
414    where
415        H: Serialize,
416        A: Serialize,
417        C: Serialize,
418    {
419        let jwt = self.sign_token_inner(&token)?;
420
421        let claims = Claims {
422            exp: token.exp,
423            host: token.iss,
424            actor_id: token.actor_id,
425            claims: token.custom,
426            inherit: None,
427        };
428
429        Ok(SignedToken::new(claims, jwt))
430    }
431
432    /// Encode and sign a new token which inherits the claims of the given `token` and includes the new `claims`.
433    pub fn consume_and_sign<H, C>(
434        &self,
435        token: SignedToken<H, A, C>,
436        host_id: H,
437        claims: C,
438        now: SystemTime,
439    ) -> Result<SignedToken<H, A, C>, Error>
440    where
441        H: Serialize + Clone,
442        A: Serialize + Clone,
443        C: Serialize + Clone,
444    {
445        let (token, claims) = Token::consume(token, now, host_id.clone(), self.id.clone(), claims)?;
446        let token = self.sign_token_inner(&token)?;
447        Ok(SignedToken::new(claims, token))
448    }
449}
450
451impl<A: Clone> Clone for Actor<A> {
452    fn clone(&self) -> Self {
453        Actor {
454            id: self.id.clone(),
455            key: match &self.key {
456                Key::Public(public_key) => Key::Public(*public_key),
457                Key::Private(keypair) => Key::Public(keypair.verifying_key()),
458            },
459        }
460    }
461}
462
463impl<A: fmt::Debug> fmt::Debug for Actor<A> {
464    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
465        write!(f, "actor {:?}", self.id)
466    }
467}
468
469#[derive(Eq, PartialEq, Debug, Deserialize, Serialize)]
470struct TokenHeader {
471    alg: String,
472    typ: String,
473}
474
475impl Default for TokenHeader {
476    fn default() -> TokenHeader {
477        TokenHeader {
478            alg: "ES256".into(),
479            typ: "JWT".into(),
480        }
481    }
482}
483
484/// The [`Claims`] of a [`SignedToken`]
485#[derive(Clone, Debug, Eq, PartialEq)]
486pub struct Claims<H, A, C> {
487    exp: u64,
488    host: H,
489    actor_id: A,
490    claims: C,
491    inherit: Option<Box<Claims<H, A, C>>>,
492}
493
494impl<H, A, C> Claims<H, A, C> {
495    fn new(exp: u64, host: H, actor_id: A, claims: C) -> Self {
496        Self {
497            exp,
498            host,
499            actor_id,
500            claims,
501            inherit: None,
502        }
503    }
504
505    fn consume(self, host: H, actor_id: A, claims: C) -> Result<Self, Error> {
506        let exp = self.expires().duration_since(UNIX_EPOCH)?;
507
508        Ok(Self {
509            exp: exp.as_secs(),
510            host,
511            actor_id,
512            claims,
513            inherit: Some(Box::new(self)),
514        })
515    }
516
517    fn expires(&self) -> SystemTime {
518        UNIX_EPOCH + Duration::from_secs(self.exp)
519    }
520}
521
522pub struct Iter<'a, H, A, C> {
523    claims: Option<&'a Claims<H, A, C>>,
524}
525
526impl<'a, H: 'a, A: 'a, C: 'a> Iterator for Iter<'a, H, A, C> {
527    type Item = (&'a H, &'a A, &'a C);
528
529    fn next(&mut self) -> Option<Self::Item> {
530        let claims = self.claims?;
531        let item = (&claims.host, &claims.actor_id, &claims.claims);
532        self.claims = claims.inherit.as_ref().map(|claims| &**claims);
533        Some(item)
534    }
535}
536
537impl<H, A, C> Claims<H, A, C> {
538    pub fn iter(&self) -> Iter<H, A, C> {
539        Iter { claims: Some(self) }
540    }
541}
542
543impl<H: PartialEq, A: PartialEq, C> Claims<H, A, C> {
544    /// Get the most recent claim made with the given `actor_id` on the given `host`, if any.
545    pub fn get(&self, host: &H, actor_id: &A) -> Option<&C> {
546        self.iter()
547            .filter_map(|(h, a, c)| {
548                if h == host && a == actor_id {
549                    Some(c)
550                } else {
551                    None
552                }
553            })
554            .next()
555    }
556}
557
558impl<'a, H, A, C> IntoIterator for &'a Claims<H, A, C> {
559    type Item = (&'a H, &'a A, &'a C);
560    type IntoIter = Iter<'a, H, A, C>;
561
562    fn into_iter(self) -> Self::IntoIter {
563        self.iter()
564    }
565}
566
567/// The JSON Web Token wire format
568#[derive(Clone, Eq, PartialEq, Deserialize, Serialize)]
569pub struct Token<H, A, C> {
570    iss: H,
571    iat: u64,
572    exp: u64,
573    actor_id: A,
574    custom: C,
575    inherit: Option<String>,
576}
577
578impl<H, A, C> Token<H, A, C> {
579    /// Create a new (unsigned) token.
580    pub fn new(iss: H, iat: SystemTime, ttl: Duration, actor_id: A, claims: C) -> Self {
581        let iat = iat.duration_since(UNIX_EPOCH).expect("duration");
582        let exp = iat + ttl;
583
584        Self {
585            iss,
586            iat: iat.as_secs(),
587            exp: exp.as_secs(),
588            actor_id,
589            custom: claims,
590            inherit: None,
591        }
592    }
593
594    fn consume(
595        parent: SignedToken<H, A, C>,
596        iat: SystemTime,
597        host_id: H,
598        actor_id: A,
599        claims: C,
600    ) -> Result<(Self, Claims<H, A, C>), Error>
601    where
602        H: Clone,
603        A: Clone,
604        C: Clone,
605    {
606        let iat = iat.duration_since(UNIX_EPOCH)?;
607        let exp = parent.expires().duration_since(UNIX_EPOCH)?;
608
609        let token = Self {
610            iss: host_id.clone(),
611            iat: iat.as_secs(),
612            exp: exp.as_secs(),
613            actor_id: actor_id.clone(),
614            custom: claims.clone(),
615            inherit: Some(parent.jwt),
616        };
617
618        let claims = parent.claims.consume(host_id, actor_id, claims)?;
619
620        Ok((token, claims))
621    }
622
623    /// Borrow the claimed issuer of this token.
624    pub fn issuer(&self) -> &H {
625        &self.iss
626    }
627
628    /// Borrow the actor to whom this token claims to belong.
629    pub fn actor_id(&self) -> &A {
630        &self.actor_id
631    }
632
633    /// Return `true` if this token is expired (or not yet issued) at the given moment.
634    pub fn is_expired(&self, now: SystemTime) -> bool {
635        let iat = UNIX_EPOCH + Duration::from_secs(self.iat);
636        let exp = UNIX_EPOCH + Duration::from_secs(self.exp);
637        now < iat || now >= exp
638    }
639}
640
641impl<H: fmt::Display, A: fmt::Display, C> fmt::Debug for Token<H, A, C> {
642    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
643        write!(
644            f,
645            "JWT token claiming to authenticate actor {} at host {}",
646            self.actor_id, self.iss
647        )
648    }
649}
650
651/// The data of a JWT including its (inherited) claims and encoded, signed representation.
652#[derive(Clone, Eq, PartialEq)]
653pub struct SignedToken<H, A, C> {
654    claims: Claims<H, A, C>,
655    jwt: String,
656}
657
658impl<H, A, C> SignedToken<H, A, C> {
659    fn new(data: Claims<H, A, C>, jwt: String) -> Self {
660        Self { claims: data, jwt }
661    }
662
663    /// Borrow the [`Claims`] of this [`SignedToken`].
664    pub fn claims(&self) -> &Claims<H, A, C> {
665        &self.claims
666    }
667
668    /// Check the expiration time of this [`SignedToken`].
669    pub fn expires(&self) -> SystemTime {
670        self.claims.expires()
671    }
672
673    /// Borrow the signed, encoded representation of this token.
674    pub fn jwt(&self) -> &str {
675        &self.jwt
676    }
677
678    /// Destructure this [`SignedToken`] into its encoded representation.
679    pub fn into_jwt(self) -> String {
680        self.jwt
681    }
682}
683
684impl<H: fmt::Debug, A: fmt::Debug, C: fmt::Debug> fmt::Debug for SignedToken<H, A, C> {
685    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
686        write!(f, "JWT {} which claims {:?}", self.jwt, self.claims)
687    }
688}
689
690fn token_signature(encoded: &str) -> Result<(&str, Signature), Error> {
691    if encoded.ends_with('.') {
692        return Err(Error::format("encoded token cannot end with ."));
693    }
694
695    let i = encoded
696        .rfind('.')
697        .ok_or_else(|| Error::format(format!("invalid token: {}", encoded)))?;
698
699    let message = &encoded[..i];
700
701    let signature = BASE64_STANDARD
702        .decode(&encoded[(i + 1)..])
703        .map_err(|e| Error::new(ErrorKind::Base64, e.to_string()))?;
704
705    let signature = Signature::try_from(&signature[..])?;
706
707    Ok((message, signature))
708}
709
710fn decode_token<H, A, C>(encoded: &str) -> Result<Token<H, A, C>, Error>
711where
712    H: DeserializeOwned,
713    A: DeserializeOwned,
714    C: DeserializeOwned,
715{
716    let i = encoded
717        .find('.')
718        .ok_or_else(|| Error::format(format!("invalid token: {}", encoded)))?;
719
720    let header = BASE64_STANDARD.decode(&encoded[..i])?;
721    let header: TokenHeader = serde_json::from_slice(&header)?;
722
723    if header != TokenHeader::default() {
724        return Err(Error::format(format!(
725            "unsupported bearer token type: {header:?}"
726        )));
727    }
728
729    let token = BASE64_STANDARD.decode(&encoded[(i + 1)..])?;
730    let token = serde_json::from_slice(&token)?;
731
732    Ok(token)
733}
734
735#[cfg(test)]
736mod tests {
737    use super::*;
738
739    const SIZE_LIMIT: usize = 8000; // max HTTP header size
740
741    #[test]
742    fn test_format() {
743        let actor = Actor::new("actor".to_string());
744        let token = Token::new(
745            "example.com".to_string(),
746            SystemTime::now(),
747            Duration::from_secs(30),
748            actor.id().to_string(),
749            (),
750        );
751
752        let signed = actor.sign_token(token).unwrap();
753        let (message, _) = token_signature(signed.jwt()).unwrap();
754
755        assert!(signed.jwt().starts_with(message));
756        assert!(signed.jwt().len() < SIZE_LIMIT);
757    }
758}