fundamentum_sdk_mqtt/security/
mod.rs

1//! Secure Authentication via JWT
2//!
3
4use chrono::{Duration, Utc};
5use derive_builder::Builder;
6use jsonwebtoken::{EncodingKey, Header, encode};
7
8pub use jsonwebtoken::Algorithm;
9
10use crate::error;
11
12mod claims;
13use claims::Claims;
14
15mod fetcher;
16pub use fetcher::SecurityFetcher;
17pub use fetcher::SecurityFileFetcher;
18
19/// Security definition
20#[derive(Builder)]
21#[builder(pattern = "owned")]
22pub struct Security {
23    /// Device's project ID
24    project_id: u64,
25    /// Device's private key
26    #[builder(setter(strip_option))]
27    fetcher: Option<Box<dyn SecurityFetcher + Send + Sync>>,
28    /// Token's expiration
29    #[builder(default = "Duration::days(365)")]
30    expiration: Duration,
31    /// Token's algorithm
32    #[builder(default = "Algorithm::RS256")]
33    algorithm: Algorithm,
34}
35
36impl Default for Security {
37    fn default() -> Self {
38        Self {
39            project_id: Default::default(),
40            fetcher: Option::default(),
41            expiration: Duration::days(365),
42            algorithm: Algorithm::RS256,
43        }
44    }
45}
46
47impl Security {
48    /// Generate a new token
49    ///
50    /// # Errors
51    ///
52    /// An error can be generated from the fetcher or the jsonwebtoken library.
53    ///
54    /// # Panics
55    ///
56    /// If the fetcher is not provided, then this function will panic
57    ///
58    pub async fn generate_token(&self) -> Result<Token, error::Error> {
59        #[allow(clippy::cast_sign_loss, clippy::cast_possible_truncation)]
60        let iat = Utc::now().timestamp() as usize;
61
62        #[allow(clippy::cast_sign_loss, clippy::cast_possible_truncation)]
63        let exp = (Utc::now() + self.expiration).timestamp() as usize;
64
65        let claims = Claims {
66            aud: Some(self.project_id.to_string()), // The audience field should always be set to the GCP project id.
67            iat: Some(iat),                         // The time that the token was issued at.
68            exp: Some(exp),
69            ..Default::default()
70        };
71
72        let header: Header = Header::new(self.algorithm);
73        let private_key = self
74            .fetcher
75            .as_ref()
76            .expect("must provide a fetcher")
77            .read_private_key()
78            .await?;
79        let encoding_key = EncodingKey::from_rsa_pem(&private_key)?;
80
81        let token = encode(&header, &claims, &encoding_key)?;
82        Ok(token)
83    }
84}
85
86pub type Token = String;