Skip to main content

tuwunel_api/client/
account_data.rs

1use axum::extract::State;
2use ruma::{
3	RoomId, UserId,
4	api::client::config::{
5		delete_global_account_data, delete_room_account_data, get_global_account_data,
6		get_room_account_data, set_global_account_data, set_room_account_data,
7	},
8	events::{
9		AnyGlobalAccountDataEventContent, AnyRoomAccountDataEventContent,
10		GlobalAccountDataEventType, RoomAccountDataEventType,
11	},
12	serde::Raw,
13};
14use serde::Deserialize;
15use serde_json::{Value as JsonValue, json, value::RawValue as RawJsonValue};
16use tuwunel_core::{Err, Result, err};
17use tuwunel_service::Services;
18
19use crate::Ruma;
20
21/// # `PUT /_matrix/client/r0/user/{userId}/account_data/{type}`
22///
23/// Sets some account data for the sender user.
24pub(crate) async fn set_global_account_data_route(
25	State(services): State<crate::State>,
26	body: Ruma<set_global_account_data::v3::Request>,
27) -> Result<set_global_account_data::v3::Response> {
28	let sender_user = body.sender_user();
29
30	if sender_user != body.user_id && body.appservice_info.is_none() {
31		return Err!(Request(Forbidden("You cannot set account data for other users.")));
32	}
33
34	set_account_data(
35		&services,
36		None,
37		&body.user_id,
38		&body.event_type.to_string(),
39		body.data.json(),
40	)
41	.await?;
42
43	Ok(set_global_account_data::v3::Response {})
44}
45
46/// # `PUT /_matrix/client/r0/user/{userId}/rooms/{roomId}/account_data/{type}`
47///
48/// Sets some room account data for the sender user.
49pub(crate) async fn set_room_account_data_route(
50	State(services): State<crate::State>,
51	body: Ruma<set_room_account_data::v3::Request>,
52) -> Result<set_room_account_data::v3::Response> {
53	let sender_user = body.sender_user();
54
55	if sender_user != body.user_id && body.appservice_info.is_none() {
56		return Err!(Request(Forbidden("You cannot set account data for other users.")));
57	}
58
59	set_account_data(
60		&services,
61		Some(&body.room_id),
62		&body.user_id,
63		&body.event_type.to_string(),
64		body.data.json(),
65	)
66	.await?;
67
68	Ok(set_room_account_data::v3::Response {})
69}
70
71/// # `GET /_matrix/client/r0/user/{userId}/account_data/{type}`
72///
73/// Gets some account data for the sender user.
74pub(crate) async fn get_global_account_data_route(
75	State(services): State<crate::State>,
76	body: Ruma<get_global_account_data::v3::Request>,
77) -> Result<get_global_account_data::v3::Response> {
78	let sender_user = body.sender_user();
79
80	if sender_user != body.user_id && body.appservice_info.is_none() {
81		return Err!(Request(Forbidden("You cannot get account data of other users.")));
82	}
83
84	let account_data: ExtractGlobalEventContent = services
85		.account_data
86		.get_global(&body.user_id, body.event_type.clone())
87		.await
88		.map_err(|_| err!(Request(NotFound("Data not found."))))?;
89
90	if is_empty_content(&account_data.content) {
91		return Err!(Request(NotFound("Data not found.")));
92	}
93
94	Ok(get_global_account_data::v3::Response { account_data: account_data.content })
95}
96
97/// # `GET /_matrix/client/r0/user/{userId}/rooms/{roomId}/account_data/{type}`
98///
99/// Gets some room account data for the sender user.
100pub(crate) async fn get_room_account_data_route(
101	State(services): State<crate::State>,
102	body: Ruma<get_room_account_data::v3::Request>,
103) -> Result<get_room_account_data::v3::Response> {
104	let sender_user = body.sender_user();
105
106	if sender_user != body.user_id && body.appservice_info.is_none() {
107		return Err!(Request(Forbidden("You cannot get account data of other users.")));
108	}
109
110	let account_data: ExtractRoomEventContent = services
111		.account_data
112		.get_room(&body.room_id, &body.user_id, body.event_type.clone())
113		.await
114		.map_err(|_| err!(Request(NotFound("Data not found."))))?;
115
116	if is_empty_content(&account_data.content) {
117		return Err!(Request(NotFound("Data not found.")));
118	}
119
120	Ok(get_room_account_data::v3::Response { account_data: account_data.content })
121}
122
123/// # `DELETE /_matrix/client/unstable/org.matrix.msc3391/user/{userId}/account_data/{type}`
124///
125/// MSC3391: erase the named global account data type for the user.
126pub(crate) async fn delete_global_account_data_route(
127	State(services): State<crate::State>,
128	body: Ruma<delete_global_account_data::unstable::Request>,
129) -> Result<delete_global_account_data::unstable::Response> {
130	let sender_user = body.sender_user();
131
132	if sender_user != body.user_id && body.appservice_info.is_none() {
133		return Err!(Request(Forbidden("You cannot delete account data for other users.")));
134	}
135
136	services
137		.account_data
138		.delete(None, &body.user_id, body.event_type.to_string().into())
139		.await?;
140
141	Ok(delete_global_account_data::unstable::Response {})
142}
143
144/// # `DELETE /_matrix/client/unstable/org.matrix.msc3391/user/{userId}/rooms/{roomId}/account_data/{type}`
145///
146/// MSC3391: erase the named room account data type for the user.
147pub(crate) async fn delete_room_account_data_route(
148	State(services): State<crate::State>,
149	body: Ruma<delete_room_account_data::unstable::Request>,
150) -> Result<delete_room_account_data::unstable::Response> {
151	let sender_user = body.sender_user();
152
153	if sender_user != body.user_id && body.appservice_info.is_none() {
154		return Err!(Request(Forbidden("You cannot delete account data for other users.")));
155	}
156
157	services
158		.account_data
159		.delete(Some(&body.room_id), &body.user_id, body.event_type.clone())
160		.await?;
161
162	Ok(delete_room_account_data::unstable::Response {})
163}
164
165async fn set_account_data(
166	services: &Services,
167	room_id: Option<&RoomId>,
168	sender_user: &UserId,
169	event_type_s: &str,
170	data: &RawJsonValue,
171) -> Result {
172	if event_type_s == RoomAccountDataEventType::FullyRead.to_cow_str() {
173		return Err!(Request(BadJson(
174			"This endpoint cannot be used for marking a room as fully read (setting \
175			 m.fully_read)"
176		)));
177	}
178
179	if event_type_s == GlobalAccountDataEventType::PushRules.to_cow_str() {
180		return Err!(Request(BadJson(
181			"This endpoint cannot be used for setting/configuring push rules."
182		)));
183	}
184
185	let data: serde_json::Value = serde_json::from_str(data.get())
186		.map_err(|e| err!(Request(BadJson(warn!("Invalid JSON provided: {e}")))))?;
187
188	services
189		.account_data
190		.update(
191			room_id,
192			sender_user,
193			event_type_s.into(),
194			&json!({
195				"type": event_type_s,
196				"content": data,
197			}),
198		)
199		.await
200}
201
202/// MSC3391: tombstoned account data carries `content: {}`. Sync delta
203/// surfaces the empty event so clients can apply the deletion; everywhere
204/// else (GET, initial sync) treats it as not-present.
205fn is_empty_content<T>(content: &Raw<T>) -> bool { is_empty_object_json(content.json()) }
206
207/// Equivalent test against a stored account-data event (`{type, content}`)
208/// rather than the bare `content` payload. Used by sync filters.
209pub(crate) fn is_empty_account_data_event<T>(event: &Raw<T>) -> bool {
210	#[derive(Deserialize)]
211	struct ContentOnly<'a> {
212		#[serde(borrow)]
213		content: &'a RawJsonValue,
214	}
215
216	serde_json::from_str::<ContentOnly<'_>>(event.json().get())
217		.ok()
218		.is_some_and(|c| is_empty_object_json(c.content))
219}
220
221fn is_empty_object_json(s: &RawJsonValue) -> bool {
222	serde_json::from_str::<JsonValue>(s.get())
223		.ok()
224		.and_then(|v| v.as_object().map(serde_json::Map::is_empty))
225		.unwrap_or(false)
226}
227
228#[derive(Deserialize)]
229struct ExtractRoomEventContent {
230	content: Raw<AnyRoomAccountDataEventContent>,
231}
232
233#[derive(Deserialize)]
234struct ExtractGlobalEventContent {
235	content: Raw<AnyGlobalAccountDataEventContent>,
236}