xmtp_db/encrypted_store/
user_preferences.rs1use super::{
2 ConnectionExt,
3 schema::user_preferences::{self, dsl},
4};
5use crate::{StorageError, Store};
6use diesel::{insert_into, prelude::*};
7use xmtp_common::time::now_ns;
8
9#[derive(
10 Identifiable, Insertable, Queryable, AsChangeset, Debug, Clone, PartialEq, Eq, Default,
11)]
12#[diesel(table_name = user_preferences)]
13#[diesel(primary_key(id))]
14pub struct StoredUserPreferences {
15 pub id: i32,
16 pub hmac_key: Option<Vec<u8>>,
18 pub hmac_key_cycled_at_ns: Option<i64>,
19 pub dm_group_updates_migrated: bool,
21}
22
23impl<C> Store<C> for StoredUserPreferences
24where
25 C: ConnectionExt,
26{
27 type Output = ();
28 fn store(&self, conn: &C) -> Result<Self::Output, StorageError> {
29 conn.raw_query_write(|conn| {
30 diesel::update(dsl::user_preferences)
31 .set(self)
32 .execute(conn)
33 })?;
34
35 Ok(())
36 }
37}
38
39#[derive(Debug)]
40pub struct HmacKey {
41 pub key: [u8; 42],
43 pub epoch: i64,
45}
46
47impl HmacKey {
48 pub fn random_key() -> Vec<u8> {
49 xmtp_common::rand_vec::<42>()
50 }
51}
52
53impl StoredUserPreferences {
54 pub fn load(conn: impl ConnectionExt) -> Result<Self, StorageError> {
55 let pref = conn.raw_query_read(|conn| dsl::user_preferences.first(conn).optional())?;
56 Ok(pref.unwrap_or_default())
57 }
58
59 fn store(&self, conn: &impl crate::DbQuery) -> Result<(), StorageError> {
60 conn.raw_query_write(|conn| {
61 insert_into(dsl::user_preferences)
62 .values(self)
63 .on_conflict(user_preferences::id)
64 .do_update()
65 .set(self)
66 .execute(conn)
67 })?;
68
69 Ok(())
70 }
71
72 pub fn store_hmac_key(
73 conn: &impl crate::DbQuery,
74 key: &[u8],
75 cycled_at: Option<i64>,
76 ) -> Result<(), StorageError> {
77 if key.len() != 42 {
78 return Err(StorageError::InvalidHmacLength);
79 }
80
81 let mut preferences = Self::load(conn)?;
82
83 if let (Some(old), Some(new)) = (preferences.hmac_key_cycled_at_ns, cycled_at)
84 && old > new
85 {
86 return Ok(());
87 }
88
89 preferences.hmac_key = Some(key.to_vec());
90 preferences.hmac_key_cycled_at_ns = Some(cycled_at.unwrap_or_else(now_ns));
91 preferences.store(conn)?;
92
93 Ok(())
94 }
95}
96
97#[cfg(test)]
98mod tests {
99 #[cfg(target_arch = "wasm32")]
100 wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_dedicated_worker);
101 use super::*;
102
103 #[xmtp_common::test]
104 fn test_insert_and_update_preferences() {
105 crate::test_utils::with_connection(|conn| {
106 let pref = StoredUserPreferences::load(conn).unwrap();
107 assert!(pref.hmac_key.is_none());
109
110 let pref = StoredUserPreferences::load(conn).unwrap();
112 assert!(pref.hmac_key.is_none());
114
115 let hmac_key = HmacKey::random_key();
117 StoredUserPreferences::store_hmac_key(conn, &hmac_key, None).unwrap();
118 let pref = StoredUserPreferences::load(conn).unwrap();
119 assert_eq!(hmac_key, pref.hmac_key.unwrap());
121
122 let query = dsl::user_preferences.order(dsl::id.desc());
124 let result = conn
125 .raw_query_read(|conn| query.load::<StoredUserPreferences>(conn))
126 .unwrap();
127 assert_eq!(result.len(), 1);
128 })
129 }
130}