tuwunel_api/client/
account_data.rs1use 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
21pub(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
46pub(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
71pub(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
97pub(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
123pub(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
144pub(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
202fn is_empty_content<T>(content: &Raw<T>) -> bool { is_empty_object_json(content.json()) }
206
207pub(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}