Skip to main content

shared/domain/signing_keys/
model.rs

1use crate::domain::query::IntoBsonDocument;
2use chrono::{DateTime, Utc};
3use mongodb::bson;
4use serde::{Deserialize, Serialize};
5use sqlx::FromRow;
6use utoipa::ToSchema;
7
8use super::super::serde::{
9    deserialize_datetime, deserialize_object_id_as_string, deserialize_option_datetime,
10};
11
12pub struct SigningKeys {
13    pub private_key: String,
14    pub key: SigningKey,
15}
16
17#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize, FromRow, ToSchema)]
18pub struct SigningKey {
19    #[serde(
20        rename = "_id",
21        default,
22        skip_serializing_if = "Option::is_none",
23        deserialize_with = "deserialize_object_id_as_string"
24    )]
25    pub id: Option<String>,
26
27    #[sqlx(rename = "createdAt")]
28    #[serde(rename = "createdAt", deserialize_with = "deserialize_datetime")]
29    pub created_at: DateTime<Utc>,
30
31    #[sqlx(rename = "rotatedAt")]
32    #[serde(rename = "rotatedAt", deserialize_with = "deserialize_option_datetime")]
33    pub rotated_at: Option<DateTime<Utc>>,
34
35    #[sqlx(rename = "expiresAt")]
36    #[serde(rename = "expiresAt", deserialize_with = "deserialize_option_datetime")]
37    pub expires_at: Option<DateTime<Utc>>,
38
39    pub status: String, // active, retired, revoked
40
41    pub encrypted_private_key: String,
42    pub public_key: String,
43    pub algorithm: String,
44    pub kid: String,
45    pub kty: String,
46}
47
48impl SigningKey {
49    pub fn id(&self) -> Result<&str, crate::error::CoreError> {
50        self.id.as_deref().ok_or_else(|| {
51            tracing::error!(
52                error_code = "ValidationError::Malformed",
53                "Unexpected null/missing data"
54            );
55            crate::error::CoreError::Validation(crate::error::ValidationError::Malformed {
56                field: crate::error::CredentialField::ObjectId,
57            })
58        })
59    }
60}
61
62impl SigningKey {
63    pub fn new(prv: &str, public: &str) -> Self {
64        Self {
65            id: None,
66            created_at: Utc::now(),
67            rotated_at: None,
68            expires_at: None,
69            status: "active".into(),
70            encrypted_private_key: prv.into(),
71            public_key: public.into(),
72            algorithm: String::from("RS256"),
73            kid: String::from("k1"),
74            kty: String::from("RSA"),
75        }
76    }
77}
78impl SigningKey {
79    pub fn with_algorithm(mut self, algorithm: &str) -> Self {
80        self.algorithm = algorithm.into();
81        self
82    }
83    pub fn with_kid(mut self, kid: &str) -> Self {
84        self.kid = kid.into();
85        self
86    }
87    pub fn with_kty(mut self, kty: &str) -> Self {
88        self.kty = kty.into();
89        self
90    }
91}
92
93impl IntoBsonDocument for SigningKey {
94    fn into_bson_document(self) -> Result<bson::Document, bson::ser::Error> {
95        let mut doc = bson::to_document(&self)?;
96
97        for key in &["createdAt", "rotatedAt", "expiresAt"] {
98            if let Some(bson::Bson::String(s)) = doc.get(*key).cloned()
99                && let Ok(dt) = DateTime::parse_from_rfc3339(&s)
100            {
101                doc.insert(
102                    *key,
103                    bson::Bson::DateTime(bson::DateTime::from_millis(dt.timestamp_millis())),
104                );
105            }
106        }
107
108        Ok(doc)
109    }
110}