Skip to main content

tuwunel_api/client/account_data/
mod.rs

1mod delete_global_account_data;
2mod delete_room_account_data;
3mod get_global_account_data;
4mod get_room_account_data;
5mod set_global_account_data;
6mod set_room_account_data;
7
8use ruma::{
9	RoomId, UserId,
10	events::{GlobalAccountDataEventType, RoomAccountDataEventType},
11	serde::Raw,
12};
13use serde::Deserialize;
14use serde_json::{Value as JsonValue, json, value::RawValue as RawJsonValue};
15use tuwunel_core::{Err, Result, err};
16use tuwunel_service::{Services, appservice::RegistrationInfo};
17
18pub(crate) use self::{
19	delete_global_account_data::delete_global_account_data_route,
20	delete_room_account_data::delete_room_account_data_route,
21	get_global_account_data::get_global_account_data_route,
22	get_room_account_data::get_room_account_data_route,
23	set_global_account_data::set_global_account_data_route,
24	set_room_account_data::set_room_account_data_route,
25};
26
27fn assert_account_data_owner(
28	sender_user: &UserId,
29	user_id: &UserId,
30	appservice_info: Option<&RegistrationInfo>,
31	message: &str,
32) -> Result {
33	if sender_user != user_id && appservice_info.is_none() {
34		return Err!(Request(Forbidden("{message}")));
35	}
36
37	Ok(())
38}
39
40async fn set_account_data(
41	services: &Services,
42	room_id: Option<&RoomId>,
43	sender_user: &UserId,
44	event_type_s: &str,
45	data: &RawJsonValue,
46) -> Result {
47	if event_type_s == RoomAccountDataEventType::FullyRead.to_cow_str() {
48		return Err!(Request(BadJson(
49			"This endpoint cannot be used for marking a room as fully read (setting \
50			 m.fully_read)"
51		)));
52	}
53
54	if event_type_s == GlobalAccountDataEventType::PushRules.to_cow_str() {
55		return Err!(Request(BadJson(
56			"This endpoint cannot be used for setting/configuring push rules."
57		)));
58	}
59
60	let data: serde_json::Value = serde_json::from_str(data.get())
61		.map_err(|e| err!(Request(BadJson(warn!("Invalid JSON provided: {e}")))))?;
62
63	services
64		.account_data
65		.update(
66			room_id,
67			sender_user,
68			event_type_s.into(),
69			&json!({
70				"type": event_type_s,
71				"content": data,
72			}),
73		)
74		.await
75}
76
77/// MSC3391: tombstoned account data carries `content: {}`. Sync delta
78/// surfaces the empty event so clients can apply the deletion; everywhere
79/// else (GET, initial sync) treats it as not-present.
80fn is_empty_content<T>(content: &Raw<T>) -> bool { is_empty_object_json(content.json()) }
81
82/// Equivalent test against a stored account-data event (`{type, content}`)
83/// rather than the bare `content` payload. Used by sync filters.
84pub(crate) fn is_empty_account_data_event<T>(event: &Raw<T>) -> bool {
85	#[derive(Deserialize)]
86	struct ContentOnly<'a> {
87		#[serde(borrow)]
88		content: &'a RawJsonValue,
89	}
90
91	serde_json::from_str::<ContentOnly<'_>>(event.json().get())
92		.is_ok_and(|c| is_empty_object_json(c.content))
93}
94
95fn is_empty_object_json(s: &RawJsonValue) -> bool {
96	serde_json::from_str::<JsonValue>(s.get())
97		.ok()
98		.and_then(|v| v.as_object().map(serde_json::Map::is_empty))
99		.unwrap_or(false)
100}