1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
//! Secure Authentication via JWT
//!

use chrono::{Duration, Utc};
use derive_builder::Builder;
use jsonwebtoken::{encode, EncodingKey, Header};

pub use jsonwebtoken::Algorithm;

use crate::error;

mod claims;
use claims::Claims;

mod fetcher;
pub use fetcher::SecurityFetcher;
pub use fetcher::SecurityFileFetcher;

/// Security definition
#[derive(Builder)]
#[builder(pattern = "owned")]
pub struct Security {
    /// Device's project_id
    project_id: u64,
    /// Device's private_key
    #[builder(setter(strip_option))]
    fetcher: Option<Box<dyn SecurityFetcher + Send + Sync>>,
    /// Token's expiration
    #[builder(default = "Duration::days(365)")]
    expiration: Duration,
    /// Token's algorithm
    #[builder(default = "Algorithm::RS256")]
    algorithm: Algorithm,
}

impl Default for Security {
    fn default() -> Self {
        Self {
            project_id: Default::default(),
            fetcher: Option::default(),
            expiration: Duration::days(365),
            algorithm: Algorithm::RS256,
        }
    }
}

impl Security {
    /// Generate a new token
    ///
    /// # Errors
    ///
    /// An error can be generated from the fetcher or the jsonwebtoken library.
    ///
    /// # Panics
    ///
    /// If the fetcher is not provided, then this function will panic
    ///
    pub async fn generate_token(&self) -> Result<Token, error::Error> {
        #[allow(clippy::cast_sign_loss, clippy::cast_possible_truncation)]
        let iat = Utc::now().timestamp() as usize;

        #[allow(clippy::cast_sign_loss, clippy::cast_possible_truncation)]
        let exp = (Utc::now() + self.expiration).timestamp() as usize;

        let claims = Claims {
            aud: Some(self.project_id.to_string()), // The audience field should always be set to the GCP project id.
            iat: Some(iat),                         // The time that the token was issued at.
            exp: Some(exp),
            ..Default::default()
        };

        let header: Header = Header::new(self.algorithm);
        let private_key = self
            .fetcher
            .as_ref()
            .expect("must provide a fetcher")
            .read_private_key()
            .await?;
        let encoding_key = EncodingKey::from_rsa_pem(&private_key)?;

        let token = encode(&header, &claims, &encoding_key)?;
        Ok(token)
    }
}

pub type Token = String;