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 self.create_device(
53 user_id,
54 Some(&request.device_id),
55 (None, None),
56 None,
57 request.initial_device_display_name.as_deref(),
58 None,
59 )
60 .await?;
61
62 trace!(device_data = ?request.device_data);
63 self.db.userid_dehydrateddevice.raw_put(
64 user_id,
65 Json(&DehydratedDevice {
66 device_id: request.device_id.clone(),
67 device_data: request.device_data,
68 }),
69 );
70
71 trace!(device_keys = ?request.device_keys);
72 self.add_device_keys(user_id, &request.device_id, &request.device_keys)
73 .await;
74
75 trace!(one_time_keys = ?request.one_time_keys);
76 self.add_one_time_keys(
77 user_id,
78 &request.device_id,
79 request
80 .one_time_keys
81 .iter()
82 .map(|(id, key)| (id.as_ref(), key)),
83 )
84 .await?;
85
86 trace!(fallback_keys = ?request.fallback_keys);
88 self.add_fallback_keys(
89 user_id,
90 &request.device_id,
91 request
92 .fallback_keys
93 .iter()
94 .map(|(id, key)| (id.as_ref(), key)),
95 )
96 .await?;
97
98 Ok(())
99}
100
101#[implement(super::Service)]
111#[tracing::instrument(
112 level = "debug",
113 skip_all,
114 fields(
115 %user_id,
116 device_id = ?maybe_device_id,
117 )
118)]
119pub(super) async fn remove_dehydrated_device(
120 &self,
121 user_id: &UserId,
122 maybe_device_id: Option<&DeviceId>,
123) -> Result<OwnedDeviceId> {
124 let Ok(device_id) = self.get_dehydrated_device_id(user_id).await else {
125 return Err!(Request(NotFound("No dehydrated device for this user.")));
126 };
127
128 if let Some(maybe_device_id) = maybe_device_id
129 && maybe_device_id != device_id
130 {
131 return Err!(Request(NotFound("Not the user's dehydrated device.")));
132 }
133
134 self.db.userid_dehydrateddevice.remove(user_id);
135
136 Ok(device_id)
137}
138
139#[implement(super::Service)]
141#[tracing::instrument(
142 level = "debug",
143 skip_all,
144 fields(%user_id)
145)]
146pub async fn get_dehydrated_device_id(&self, user_id: &UserId) -> Result<OwnedDeviceId> {
147 self.get_dehydrated_device(user_id)
148 .await
149 .map(|device| device.device_id)
150}
151
152#[implement(super::Service)]
154#[tracing::instrument(
155 level = "debug",
156 skip_all,
157 fields(%user_id),
158 ret,
159)]
160pub async fn get_dehydrated_device(&self, user_id: &UserId) -> Result<DehydratedDevice> {
161 self.db
162 .userid_dehydrateddevice
163 .get(user_id)
164 .await
165 .deserialized::<String>()
166 .and_then(|raw| serde_json::from_str(&raw).map_err(Into::into))
167}