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 Ok(())
87}
88
89#[implement(super::Service)]
99#[tracing::instrument(
100 level = "debug",
101 skip_all,
102 fields(
103 %user_id,
104 device_id = ?maybe_device_id,
105 )
106)]
107pub(super) async fn remove_dehydrated_device(
108 &self,
109 user_id: &UserId,
110 maybe_device_id: Option<&DeviceId>,
111) -> Result<OwnedDeviceId> {
112 let Ok(device_id) = self.get_dehydrated_device_id(user_id).await else {
113 return Err!(Request(NotFound("No dehydrated device for this user.")));
114 };
115
116 if let Some(maybe_device_id) = maybe_device_id
117 && maybe_device_id != device_id
118 {
119 return Err!(Request(NotFound("Not the user's dehydrated device.")));
120 }
121
122 self.db.userid_dehydrateddevice.remove(user_id);
123
124 Ok(device_id)
125}
126
127#[implement(super::Service)]
129#[tracing::instrument(
130 level = "debug",
131 skip_all,
132 fields(%user_id)
133)]
134pub async fn get_dehydrated_device_id(&self, user_id: &UserId) -> Result<OwnedDeviceId> {
135 self.get_dehydrated_device(user_id)
136 .await
137 .map(|device| device.device_id)
138}
139
140#[implement(super::Service)]
142#[tracing::instrument(
143 level = "debug",
144 skip_all,
145 fields(%user_id),
146 ret,
147)]
148pub async fn get_dehydrated_device(&self, user_id: &UserId) -> Result<DehydratedDevice> {
149 self.db
150 .userid_dehydrateddevice
151 .get(user_id)
152 .await
153 .deserialized::<String>()
154 .and_then(|raw| serde_json::from_str(&raw).map_err(Into::into))
155}