Skip to main content

tuwunel_api/client/
device.rs

1use axum::extract::State;
2use futures::StreamExt;
3use ruma::{
4	MilliSecondsSinceUnixEpoch,
5	api::client::device::{
6		self, delete_device, delete_devices, get_device, get_devices, update_device,
7	},
8};
9use tuwunel_core::{Err, Result, debug, err, utils::string::to_small_string};
10
11use crate::{ClientIp, Ruma, router::auth_uiaa};
12
13/// # `GET /_matrix/client/r0/devices`
14///
15/// Get metadata on all devices of the sender user.
16pub(crate) async fn get_devices_route(
17	State(services): State<crate::State>,
18	body: Ruma<get_devices::v3::Request>,
19) -> Result<get_devices::v3::Response> {
20	let devices: Vec<device::Device> = services
21		.users
22		.all_devices_metadata(body.sender_user())
23		.collect()
24		.await;
25
26	Ok(get_devices::v3::Response { devices })
27}
28
29/// # `GET /_matrix/client/r0/devices/{deviceId}`
30///
31/// Get metadata on a single device of the sender user.
32pub(crate) async fn get_device_route(
33	State(services): State<crate::State>,
34	body: Ruma<get_device::v3::Request>,
35) -> Result<get_device::v3::Response> {
36	let device = services
37		.users
38		.get_device_metadata(body.sender_user(), &body.body.device_id)
39		.await
40		.map_err(|_| err!(Request(NotFound("Device not found."))))?;
41
42	Ok(get_device::v3::Response { device })
43}
44
45/// # `PUT /_matrix/client/r0/devices/{deviceId}`
46///
47/// Updates the metadata on a given device of the sender user.
48#[tracing::instrument(skip_all, fields(%client), name = "update_device")]
49pub(crate) async fn update_device_route(
50	State(services): State<crate::State>,
51	ClientIp(client): ClientIp,
52	body: Ruma<update_device::v3::Request>,
53) -> Result<update_device::v3::Response> {
54	let sender_user = body.sender_user();
55	let appservice = body.appservice_info.as_ref();
56
57	match services
58		.users
59		.get_device_metadata(sender_user, &body.device_id)
60		.await
61	{
62		| Ok(mut device) => {
63			let notify = device.display_name != body.display_name;
64			device.display_name.clone_from(&body.display_name);
65
66			device
67				.last_seen_ip
68				.clone_from(&Some(to_small_string(client)));
69
70			device
71				.last_seen_ts
72				.clone_from(&Some(MilliSecondsSinceUnixEpoch::now()));
73
74			assert_eq!(device.device_id, body.device_id, "device_id mismatch");
75			services
76				.users
77				.put_device_metadata(sender_user, notify, &device);
78
79			Ok(update_device::v3::Response {})
80		},
81		| Err(_) => {
82			let Some(appservice) = appservice else {
83				return Err!(Request(NotFound("Device not found.")));
84			};
85
86			if !appservice.registration.device_management {
87				return Err!(Request(NotFound("Device management not enabled for appservice.")));
88			}
89
90			debug!(
91				"Creating new device for {sender_user} from appservice {} as MSC4190 is enabled \
92				 and device ID does not exist",
93				appservice.registration.id
94			);
95
96			services
97				.users
98				.create_device(
99					sender_user,
100					Some(&body.device_id),
101					(None, None),
102					None,
103					body.display_name.as_deref(),
104					Some(client),
105				)
106				.await?;
107
108			return Ok(update_device::v3::Response {});
109		},
110	}
111}
112
113/// # `DELETE /_matrix/client/r0/devices/{deviceId}`
114///
115/// Deletes the given device.
116///
117/// - Requires UIAA to verify user password
118/// - Invalidates access token
119/// - Deletes device metadata (device id, device display name, last seen ip,
120///   last seen ts)
121/// - Forgets to-device events
122/// - Triggers device list updates
123pub(crate) async fn delete_device_route(
124	State(services): State<crate::State>,
125	body: Ruma<delete_device::v3::Request>,
126) -> Result<delete_device::v3::Response> {
127	let appservice = body.appservice_info.as_ref();
128
129	if appservice.is_some_and(|appservice| appservice.registration.device_management) {
130		let sender_user = body.sender_user();
131		debug!(
132			"Skipping UIAA for {sender_user} as this is from an appservice and MSC4190 is \
133			 enabled"
134		);
135		services
136			.users
137			.remove_device(sender_user, &body.device_id)
138			.await;
139
140		return Ok(delete_device::v3::Response {});
141	}
142
143	let ref sender_user = auth_uiaa(&services, &body).await?;
144
145	services
146		.users
147		.remove_device(sender_user, &body.device_id)
148		.await;
149
150	Ok(delete_device::v3::Response {})
151}
152
153/// # `POST /_matrix/client/v3/delete_devices`
154///
155/// Deletes the given list of devices.
156///
157/// - Requires UIAA to verify user password unless from an appservice with
158///   MSC4190 enabled.
159///
160/// For each device:
161/// - Invalidates access token
162/// - Deletes device metadata (device id, device display name, last seen ip,
163///   last seen ts)
164/// - Forgets to-device events
165/// - Triggers device list updates
166pub(crate) async fn delete_devices_route(
167	State(services): State<crate::State>,
168	body: Ruma<delete_devices::v3::Request>,
169) -> Result<delete_devices::v3::Response> {
170	let appservice = body.appservice_info.as_ref();
171
172	if appservice.is_some_and(|appservice| appservice.registration.device_management) {
173		let sender_user = body.sender_user();
174		debug!(
175			"Skipping UIAA for {sender_user} as this is from an appservice and MSC4190 is \
176			 enabled"
177		);
178		for device_id in &body.devices {
179			services
180				.users
181				.remove_device(sender_user, device_id)
182				.await;
183		}
184
185		return Ok(delete_devices::v3::Response {});
186	}
187
188	let ref sender_user = auth_uiaa(&services, &body).await?;
189
190	for device_id in &body.devices {
191		services
192			.users
193			.remove_device(sender_user, device_id)
194			.await;
195	}
196
197	Ok(delete_devices::v3::Response {})
198}