tuwunel_api_client/account_data/
mod.rs1mod 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#[cfg(test)]
8mod tests;
9
10use ruma::{
11 RoomId, UserId,
12 events::{GlobalAccountDataEventType, RoomAccountDataEventType},
13 serde::Raw,
14};
15use serde::Deserialize;
16use serde_json::{Value as JsonValue, json, value::RawValue as RawJsonValue};
17use tuwunel_core::{Err, Result, err};
18use tuwunel_service::{Services, appservice::RegistrationInfo};
19
20pub(crate) use self::{
21 delete_global_account_data::delete_global_account_data_route,
22 delete_room_account_data::delete_room_account_data_route,
23 get_global_account_data::get_global_account_data_route,
24 get_room_account_data::get_room_account_data_route,
25 set_global_account_data::set_global_account_data_route,
26 set_room_account_data::set_room_account_data_route,
27};
28
29fn assert_account_data_owner(
30 sender_user: &UserId,
31 user_id: &UserId,
32 appservice_info: Option<&RegistrationInfo>,
33 message: &str,
34) -> Result {
35 if sender_user != user_id
36 && !appservice_info.is_some_and(|registration| registration.is_user_match(user_id))
37 {
38 return Err!(Request(Forbidden("{message}")));
39 }
40
41 Ok(())
42}
43
44async fn set_account_data(
45 services: &Services,
46 room_id: Option<&RoomId>,
47 sender_user: &UserId,
48 event_type_s: &str,
49 data: &RawJsonValue,
50) -> Result {
51 validate_event_type(event_type_s)?;
52
53 let data: serde_json::Value = serde_json::from_str(data.get())
54 .map_err(|e| err!(Request(BadJson(warn!("Invalid JSON provided: {e}")))))?;
55
56 services
57 .account_data
58 .update(
59 room_id,
60 sender_user,
61 event_type_s.into(),
62 &json!({
63 "type": event_type_s,
64 "content": data,
65 }),
66 )
67 .await
68}
69
70fn validate_event_type(event_type: &str) -> Result {
71 if event_type == RoomAccountDataEventType::FullyRead.to_cow_str() {
72 return Err!(Request(BadJson(
73 "This endpoint cannot be used for marking a room as fully read (setting \
74 m.fully_read)"
75 )));
76 }
77
78 if event_type == GlobalAccountDataEventType::PushRules.to_cow_str() {
79 return Err!(Request(BadJson(
80 "This endpoint cannot be used for setting/configuring push rules."
81 )));
82 }
83
84 Ok(())
85}
86
87fn is_empty_content<T>(content: &Raw<T>) -> bool { is_empty_object_json(content.json()) }
91
92pub(crate) fn is_empty_account_data_event<T>(event: &Raw<T>) -> bool {
95 #[derive(Deserialize)]
96 struct ContentOnly<'a> {
97 #[serde(borrow)]
98 content: &'a RawJsonValue,
99 }
100
101 serde_json::from_str::<ContentOnly<'_>>(event.json().get())
102 .is_ok_and(|c| is_empty_object_json(c.content))
103}
104
105fn is_empty_object_json(s: &RawJsonValue) -> bool {
106 serde_json::from_str::<JsonValue>(s.get())
107 .ok()
108 .and_then(|v| v.as_object().map(serde_json::Map::is_empty))
109 .unwrap_or(false)
110}