nullnet_libtoken/
lib.rs

1mod models;
2
3use base64::Engine as _;
4use serde::Deserialize;
5use std::time::{SystemTime, UNIX_EPOCH};
6
7pub use models::{Account, Device, Organization};
8
9const EXPIRATION_MARGIN: u64 = 60 * 5;
10
11/// Represents a decoded JWT payload containing account information and metadata.
12/// Includes issue and expiration times for the token.
13#[derive(Debug, Deserialize)]
14pub struct Token {
15    pub account: Account,
16    pub iat: u64,
17    pub exp: u64,
18    #[serde(skip)]
19    pub jwt: String,
20}
21
22impl Token {
23    /// Decodes a JWT and parses its payload into a `Token` struct.
24    ///
25    /// # Arguments
26    /// * `jwt` - A JWT string consisting of three parts separated by periods (`.`).
27    ///
28    /// # Returns
29    /// * `Ok(Token)` if the token is successfully decoded and parsed.
30    /// * `Err(Error)` if the token is malformed, Base64 decoding fails, or payload deserialization fails.
31    #[allow(clippy::missing_errors_doc)]
32    pub fn from_jwt(jwt: &str) -> Result<Self, String> {
33        let parts: Vec<&str> = jwt.split('.').collect();
34
35        if parts.len() != 3 {
36            return Err(String::from("Malformed JWT"));
37        }
38
39        let decoded_payload = base64::engine::general_purpose::URL_SAFE_NO_PAD
40            .decode(parts[1])
41            .map_err(|e| e.to_string())?;
42
43        let mut token: Token =
44            serde_json::from_slice(&decoded_payload).map_err(|e| e.to_string())?;
45        token.jwt = jwt.to_string();
46
47        Ok(token)
48    }
49
50    /// Checks if the token has expired.
51    #[must_use]
52    pub fn is_expired(&self) -> bool {
53        // consider the token expired if duration_since fails
54        let Ok(duration) = SystemTime::now().duration_since(UNIX_EPOCH) else {
55            return true;
56        };
57        self.exp <= (duration.as_secs() - EXPIRATION_MARGIN)
58    }
59}