xmtp_db/encrypted_store/
identity.rs1use 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#[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}