ts_token/
jwt.rs

1//! A JSON Web Token.
2
3use core::time::Duration;
4
5use base64ct::{Base64UrlUnpadded, Encoding};
6use serde::{Deserialize, Serialize};
7
8/// A decoded JSON web token.
9/// <https://www.rfc-editor.org/rfc/rfc7519>
10#[derive(Debug, Clone)]
11pub struct JsonWebToken {
12    /// The header.
13    pub header: Header,
14    /// The claims.
15    pub claims: Claims,
16    /// The signature.
17    pub signature: Vec<u8>,
18}
19impl JsonWebToken {
20    /// Serialize the token as a JSON web token string.
21    pub fn serialize(&self) -> String {
22        let header = self.header.encode();
23        let claims = self.claims.encode();
24        let signature = Base64UrlUnpadded::encode_string(&self.signature);
25
26        format!("{header}.{claims}.{signature}")
27    }
28
29    /// Deserialize the token from a JSON web token string.
30    pub fn deserialize(value: &str) -> Option<Self> {
31        let mut parts = value.split(".");
32        let header = parts.next()?;
33        let claims = parts.next()?;
34        let signature = parts.next()?;
35
36        let header = serde_json::from_slice(&Base64UrlUnpadded::decode_vec(header).ok()?).ok()?;
37        let claims = serde_json::from_slice(&Base64UrlUnpadded::decode_vec(claims).ok()?).ok()?;
38        let signature = Base64UrlUnpadded::decode_vec(signature).ok()?;
39
40        Some(Self {
41            header,
42            claims,
43            signature,
44        })
45    }
46
47    /// Get the message to be signed.
48    pub fn message(&self) -> Vec<u8> {
49        Self::create_message(&self.header, &self.claims)
50    }
51
52    /// Create a message to be signed.
53    pub fn create_message(header: &Header, claims: &Claims) -> Vec<u8> {
54        format!("{}.{}", header.encode(), claims.encode()).into_bytes()
55    }
56}
57
58/// The JSON web token header.
59/// <https://www.rfc-editor.org/rfc/rfc7519#section-5>
60#[derive(Debug, Clone, Serialize, Deserialize)]
61pub struct Header {
62    /// The algorithm used to sign the token.
63    pub alg: String,
64
65    /// The type of algorithm used to sign the token.
66    pub typ: String,
67
68    /// The ID of the key used to sign the token.
69    pub kid: String,
70}
71impl Header {
72    /// Encode the JSON representation of the header as URL base-64.
73    ///
74    /// ## Panics
75    /// * If serializing to JSON fails.
76    pub(crate) fn encode(&self) -> String {
77        let json = serde_json::to_vec(&self).expect("serializing the header should never fail");
78        Base64UrlUnpadded::encode_string(&json)
79    }
80}
81
82/// The JSON web token claims.
83/// <https://www.rfc-editor.org/rfc/rfc7519#section-4>
84#[derive(Debug, Clone, Deserialize, Serialize)]
85pub struct Claims {
86    /// The expiry of the token, seconds since Unix epoch.
87    pub exp: u64,
88
89    /// The time when the token was issued, seconds since Unix epoch.
90    pub iat: u64,
91
92    /// The subject of the token.
93    pub sub: String,
94
95    /// The type of the token.
96    #[serde(flatten)]
97    pub typ: TokenType,
98}
99impl Claims {
100    /// Encode the JSON representation of the claims as URL base-64.
101    ///
102    /// ## Panics
103    /// * If serializing to JSON fails.
104    pub(crate) fn encode(&self) -> String {
105        let json = serde_json::to_vec(&self).expect("serializing the claims should never fail");
106        Base64UrlUnpadded::encode_string(&json)
107    }
108
109    /// Returns if the claims are valid:
110    /// * Not expired.
111    /// * Not before the issued at.
112    pub fn is_valid(&self) -> bool {
113        let exp = Duration::from_secs(self.exp);
114        let iat = Duration::from_secs(self.iat);
115        let Ok(now) = std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH) else {
116            return false;
117        };
118
119        exp > now && iat < now
120    }
121}
122
123/// The type of token.
124#[derive(Debug, Clone, Deserialize, Serialize, PartialEq)]
125#[serde(rename_all = "camelCase")]
126#[serde(tag = "typ")]
127#[non_exhaustive]
128pub enum TokenType {
129    /// A common token that grants the bearer authorisation for common actions.
130    Common,
131    /// A consent token that grants the bearer authorisation to perform a specific action.
132    Consent {
133        /// The action the bearer is authorised to perform.
134        act: String,
135    },
136    /// A token to granted when provisioning a new identity before any credentials have been added.
137    Provisioning,
138}
139impl TokenType {
140    /// How long this token type is valid for.
141    pub fn valid_for(&self) -> Duration {
142        match &self {
143            Self::Common => Duration::from_secs(60 * 60 * 24 * 30),
144            Self::Consent { .. } => Duration::from_secs(60 * 5),
145            Self::Provisioning => Duration::from_secs(60 * 60 * 4),
146        }
147    }
148}