tuwunel_service/users/
dehydrated_device.rs1use ruma::{
2 DeviceId, OwnedDeviceId, UserId,
3 api::client::dehydrated_device::{
4 DehydratedDeviceData, put_dehydrated_device::unstable::Request,
5 },
6 serde::Raw,
7};
8use serde::{Deserialize, Serialize};
9use tuwunel_core::{Err, Result, implement, trace};
10use tuwunel_database::{Deserialized, Json};
11
12#[derive(Clone, Debug, Serialize, Deserialize)]
13pub struct DehydratedDevice {
14 pub device_id: OwnedDeviceId,
16
17 pub device_data: Raw<DehydratedDeviceData>,
19}
20
21#[implement(super::Service)]
23#[tracing::instrument(
24 level = "info",
25 skip_all,
26 fields(
27 %user_id,
28 device_id = %request.device_id,
29 display_name = ?request.initial_device_display_name,
30 )
31)]
32pub async fn set_dehydrated_device(&self, user_id: &UserId, request: Request) -> Result {
33 assert!(
34 self.exists(user_id).await,
35 "Tried to create dehydrated device for non-existent user"
36 );
37
38 let existing_id = self.get_dehydrated_device_id(user_id).await;
39
40 if existing_id.is_err()
41 && self
42 .device_exists(user_id, &request.device_id)
43 .await
44 {
45 return Err!("A hydrated device already exists with that ID.");
46 }
47
48 if let Ok(existing_id) = existing_id {
49 self.remove_device(user_id, &existing_id).await;
50 }
51
52 let device_id = self
53 .create_device(
54 user_id,
55 Some(&request.device_id),
56 (None, None),
57 None,
58 request.initial_device_display_name.as_deref(),
59 None,
60 )
61 .await?;
62
63 trace!(device_data = ?request.device_data);
64 self.db.userid_dehydrateddevice.raw_put(
65 user_id,
66 Json(&DehydratedDevice {
67 device_id: device_id.clone(),
68 device_data: request.device_data,
69 }),
70 );
71
72 trace!(device_keys = ?request.device_keys);
73 self.add_device_keys(user_id, &device_id, &request.device_keys)
74 .await;
75
76 trace!(one_time_keys = ?request.one_time_keys);
77 self.add_one_time_keys(
78 user_id,
79 &device_id,
80 &request.one_time_keys,
81 request.one_time_keys.len(),
82 )
83 .await?;
84
85 trace!(fallback_keys = ?request.fallback_keys);
87 self.add_fallback_keys(
88 user_id,
89 &device_id,
90 request
91 .fallback_keys
92 .iter()
93 .map(|(id, key)| (id.as_ref(), key)),
94 )
95 .await?;
96
97 Ok(())
98}
99
100#[implement(super::Service)]
110#[tracing::instrument(
111 level = "debug",
112 skip_all,
113 fields(
114 %user_id,
115 device_id = ?maybe_device_id,
116 )
117)]
118pub(super) async fn remove_dehydrated_device(
119 &self,
120 user_id: &UserId,
121 maybe_device_id: Option<&DeviceId>,
122) -> Result<OwnedDeviceId> {
123 let Ok(device_id) = self.get_dehydrated_device_id(user_id).await else {
124 return Err!(Request(NotFound("No dehydrated device for this user.")));
125 };
126
127 if let Some(maybe_device_id) = maybe_device_id
128 && maybe_device_id != device_id
129 {
130 return Err!(Request(NotFound("Not the user's dehydrated device.")));
131 }
132
133 self.db.userid_dehydrateddevice.remove(user_id);
134
135 Ok(device_id)
136}
137
138#[implement(super::Service)]
140#[tracing::instrument(
141 level = "debug",
142 skip_all,
143 fields(%user_id)
144)]
145pub async fn get_dehydrated_device_id(&self, user_id: &UserId) -> Result<OwnedDeviceId> {
146 self.get_dehydrated_device(user_id)
147 .await
148 .map(|device| device.device_id)
149}
150
151#[implement(super::Service)]
153#[tracing::instrument(
154 level = "debug",
155 skip_all,
156 fields(%user_id),
157 ret,
158)]
159pub async fn get_dehydrated_device(&self, user_id: &UserId) -> Result<DehydratedDevice> {
160 self.db
161 .userid_dehydrateddevice
162 .get(user_id)
163 .await
164 .deserialized::<String>()
165 .and_then(|raw| serde_json::from_str(&raw).map_err(Into::into))
166}