xmtp_db/encrypted_store/
identity.rs

1use crate::encrypted_store::schema::identity;
2use crate::schema::identity::dsl;
3use crate::{ConnectionExt, DbConnection, StorageError, impl_fetch, impl_store};
4use derive_builder::Builder;
5use diesel::prelude::*;
6use serde::{Deserialize, Serialize};
7use xmtp_common::time::now_ns;
8use xmtp_configuration::KEY_PACKAGE_QUEUE_INTERVAL_NS;
9
10/// Identity of this installation
11/// There can only be one.
12#[derive(Insertable, Queryable, Debug, Clone, Builder, Serialize, Deserialize)]
13#[diesel(table_name = identity)]
14#[builder(setter(into), build_fn(error = "crate::StorageError"))]
15pub struct StoredIdentity {
16    pub inbox_id: String,
17    pub installation_keys: Vec<u8>,
18    pub credential_bytes: Vec<u8>,
19    #[builder(setter(skip))]
20    rowid: Option<i32>,
21    pub next_key_package_rotation_ns: Option<i64>,
22}
23
24impl_fetch!(StoredIdentity, identity);
25impl_store!(StoredIdentity, identity);
26
27impl StoredIdentity {
28    pub fn builder() -> StoredIdentityBuilder {
29        StoredIdentityBuilder::default()
30    }
31
32    pub fn new(inbox_id: String, installation_keys: Vec<u8>, credential_bytes: Vec<u8>) -> Self {
33        Self {
34            inbox_id,
35            installation_keys,
36            credential_bytes,
37            rowid: None,
38            next_key_package_rotation_ns: None,
39        }
40    }
41}
42pub trait QueryIdentity {
43    fn queue_key_package_rotation(&self) -> Result<(), StorageError>;
44    fn reset_key_package_rotation_queue(
45        &self,
46        rotation_interval_ns: i64,
47    ) -> Result<(), StorageError>;
48    fn is_identity_needs_rotation(&self) -> Result<bool, StorageError>;
49}
50
51impl<T> QueryIdentity for &T
52where
53    T: QueryIdentity,
54{
55    fn queue_key_package_rotation(&self) -> Result<(), StorageError> {
56        (**self).queue_key_package_rotation()
57    }
58
59    fn reset_key_package_rotation_queue(
60        &self,
61        rotation_interval_ns: i64,
62    ) -> Result<(), StorageError> {
63        (**self).reset_key_package_rotation_queue(rotation_interval_ns)
64    }
65
66    fn is_identity_needs_rotation(&self) -> Result<bool, StorageError> {
67        (**self).is_identity_needs_rotation()
68    }
69}
70
71impl<C: ConnectionExt> QueryIdentity for DbConnection<C> {
72    fn queue_key_package_rotation(&self) -> Result<(), StorageError> {
73        self.raw_query_write(|conn| {
74            let rotate_at_ns = now_ns() + KEY_PACKAGE_QUEUE_INTERVAL_NS;
75            diesel::update(dsl::identity)
76                .filter(dsl::next_key_package_rotation_ns.gt(rotate_at_ns))
77                .set(dsl::next_key_package_rotation_ns.eq(rotate_at_ns))
78                .execute(conn)?;
79
80            Ok(())
81        })?;
82
83        Ok(())
84    }
85
86    fn reset_key_package_rotation_queue(
87        &self,
88        rotation_interval_ns: i64,
89    ) -> Result<(), StorageError> {
90        use crate::schema::identity::dsl;
91
92        self.raw_query_write(|conn| {
93            diesel::update(dsl::identity)
94                .filter(
95                    dsl::next_key_package_rotation_ns
96                        .is_null()
97                        .or(dsl::next_key_package_rotation_ns.le(now_ns())),
98                )
99                .set(dsl::next_key_package_rotation_ns.eq(Some(now_ns() + rotation_interval_ns)))
100                .execute(conn)?;
101            Ok(())
102        })?;
103
104        Ok(())
105    }
106
107    fn is_identity_needs_rotation(&self) -> Result<bool, StorageError> {
108        use crate::schema::identity::dsl;
109
110        let next_rotation_opt: Option<i64> = self.raw_query_read(|conn| {
111            dsl::identity
112                .select(dsl::next_key_package_rotation_ns)
113                .first::<Option<i64>>(conn)
114        })?;
115
116        Ok(match next_rotation_opt {
117            Some(rotate_at) => now_ns() >= rotate_at,
118            None => true,
119        })
120    }
121}
122
123#[cfg(test)]
124pub(crate) mod tests {
125    #[cfg(target_arch = "wasm32")]
126    wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_dedicated_worker);
127
128    use super::StoredIdentity;
129    use crate::{Store, XmtpTestDb};
130    use xmtp_common::rand_vec;
131
132    #[xmtp_common::test]
133    async fn can_only_store_one_identity() {
134        let store = crate::TestDb::create_ephemeral_store().await;
135        let conn = &store.conn();
136
137        StoredIdentity::new("".to_string(), rand_vec::<24>(), rand_vec::<24>())
138            .store(conn)
139            .unwrap();
140
141        let duplicate_insertion =
142            StoredIdentity::new("".to_string(), rand_vec::<24>(), rand_vec::<24>()).store(conn);
143        assert!(duplicate_insertion.is_err());
144    }
145}