1use axum::extract::State;
2use futures::StreamExt;
3use ruma::{
4 OwnedRoomId,
5 api::{
6 client::{
7 membership::mutual_rooms,
8 profile::{delete_profile_field, get_profile_field, set_profile_field},
9 },
10 federation,
11 },
12 presence::PresenceState,
13 profile::{ProfileFieldName, ProfileFieldValue},
14};
15use tuwunel_core::{Err, Result, err};
16use tuwunel_service::users::propagation_default;
17
18use super::profile::{profile_mxc, profile_str, resolve_propagation};
19use crate::{ClientIp, Ruma};
20
21#[tracing::instrument(skip_all, fields(%client), name = "mutual_rooms")]
29pub(crate) async fn get_mutual_rooms_route(
30 State(services): State<crate::State>,
31 ClientIp(client): ClientIp,
32 body: Ruma<mutual_rooms::unstable::Request>,
33) -> Result<mutual_rooms::unstable::Response> {
34 let sender_user = body.sender_user();
35
36 if sender_user == body.user_id {
37 return Err!(Request(Unknown("You cannot request rooms in common with yourself.")));
38 }
39
40 if !services.users.exists(&body.user_id).await {
41 return Ok(mutual_rooms::unstable::Response { joined: vec![], next_batch_token: None });
42 }
43
44 let mutual_rooms: Vec<OwnedRoomId> = services
45 .state_cache
46 .get_shared_rooms(sender_user, &body.user_id)
47 .map(ToOwned::to_owned)
48 .collect()
49 .await;
50
51 Ok(mutual_rooms::unstable::Response {
52 joined: mutual_rooms,
53 next_batch_token: None,
54 })
55}
56
57pub(crate) async fn set_profile_field_route(
65 State(services): State<crate::State>,
66 ClientIp(client): ClientIp,
67 body: Ruma<set_profile_field::v3::Request>,
68) -> Result<set_profile_field::v3::Response> {
69 let sender_user = body.sender_user();
70
71 if *sender_user != body.user_id && body.appservice_info.is_none() {
72 return Err!(Request(Forbidden("You cannot update the profile of another user")));
73 }
74
75 if matches!(body.value, ProfileFieldValue::DisplayName(_) | ProfileFieldValue::AvatarUrl(_))
78 && services.users.is_suspended(sender_user).await
79 {
80 return Err!(Request(UserSuspended("Account is suspended.")));
81 }
82
83 if body.value.field_name().as_str().len() > 128 {
84 return Err!(Request(BadJson("Key names cannot be longer than 128 bytes")));
85 }
86
87 let propagation = resolve_propagation(
88 &body.propagate_to,
89 propagation_default(
90 services
91 .server
92 .config
93 .preserve_room_profile_overrides,
94 ),
95 );
96
97 match &body.value {
98 | ProfileFieldValue::DisplayName(displayname) => {
99 let all_joined_rooms: Vec<OwnedRoomId> = services
100 .state_cache
101 .rooms_joined(&body.user_id)
102 .map(Into::into)
103 .collect()
104 .await;
105
106 services
107 .users
108 .update_displayname(
109 &body.user_id,
110 Some(displayname),
111 &all_joined_rooms,
112 propagation,
113 )
114 .await;
115 },
116 | ProfileFieldValue::AvatarUrl(avatar_url) => {
117 let all_joined_rooms: Vec<OwnedRoomId> = services
118 .state_cache
119 .rooms_joined(&body.user_id)
120 .map(Into::into)
121 .collect()
122 .await;
123
124 services
125 .users
126 .update_avatar_url(
127 &body.user_id,
128 Some(avatar_url),
129 None,
130 &all_joined_rooms,
131 propagation,
132 )
133 .await;
134 },
135 | _ => {
136 services.users.set_profile_key(
137 &body.user_id,
138 body.value.field_name().as_str(),
139 Some(&body.value.value()),
140 );
141 },
142 }
143
144 services
146 .presence
147 .maybe_ping_presence(
148 &body.user_id,
149 body.sender_device.as_deref(),
150 Some(client),
151 &PresenceState::Online,
152 )
153 .await?;
154
155 Ok(set_profile_field::v3::Response {})
156}
157
158pub(crate) async fn delete_profile_field_route(
164 State(services): State<crate::State>,
165 ClientIp(client): ClientIp,
166 body: Ruma<delete_profile_field::v3::Request>,
167) -> Result<delete_profile_field::v3::Response> {
168 let sender_user = body.sender_user();
169
170 if *sender_user != body.user_id && body.appservice_info.is_none() {
171 return Err!(Request(Forbidden("You cannot update the profile of another user")));
172 }
173
174 if matches!(body.field, ProfileFieldName::DisplayName | ProfileFieldName::AvatarUrl)
177 && services.users.is_suspended(sender_user).await
178 {
179 return Err!(Request(UserSuspended("Account is suspended.")));
180 }
181
182 let propagation = resolve_propagation(
183 &body.propagate_to,
184 propagation_default(
185 services
186 .server
187 .config
188 .preserve_room_profile_overrides,
189 ),
190 );
191
192 match body.field {
193 | ProfileFieldName::DisplayName => {
194 let all_joined_rooms: Vec<OwnedRoomId> = services
195 .state_cache
196 .rooms_joined(&body.user_id)
197 .map(Into::into)
198 .collect()
199 .await;
200
201 services
202 .users
203 .update_displayname(&body.user_id, None, &all_joined_rooms, propagation)
204 .await;
205 },
206 | ProfileFieldName::AvatarUrl => {
207 let all_joined_rooms: Vec<OwnedRoomId> = services
208 .state_cache
209 .rooms_joined(&body.user_id)
210 .map(Into::into)
211 .collect()
212 .await;
213
214 services
215 .users
216 .update_avatar_url(&body.user_id, None, None, &all_joined_rooms, propagation)
217 .await;
218 },
219 | _ => {
220 services
221 .users
222 .set_profile_key(&body.user_id, body.field.as_str(), None);
223 },
224 }
225
226 services
228 .presence
229 .maybe_ping_presence(
230 &body.user_id,
231 body.sender_device.as_deref(),
232 Some(client),
233 &PresenceState::Online,
234 )
235 .await?;
236
237 Ok(delete_profile_field::v3::Response {})
238}
239
240pub(crate) async fn get_profile_field_route(
247 State(services): State<crate::State>,
248 body: Ruma<get_profile_field::v3::Request>,
249) -> Result<get_profile_field::v3::Response> {
250 if !services.globals.user_is_local(&body.user_id) {
251 if let Ok(response) = services
253 .federation
254 .execute(
255 body.user_id.server_name(),
256 federation::query::get_profile_information::v1::Request {
257 user_id: body.user_id.clone(),
258 field: None, },
260 )
261 .await
262 {
263 if !services.users.exists(&body.user_id).await {
264 services
265 .users
266 .create(&body.user_id, None, None)
267 .await?;
268 }
269
270 services
271 .users
272 .set_displayname(&body.user_id, profile_str(&response, "displayname"));
273
274 services
275 .users
276 .set_avatar_url(&body.user_id, profile_mxc(&response, "avatar_url"));
277
278 services
279 .users
280 .set_blurhash(&body.user_id, profile_str(&response, "blurhash"));
281
282 services
283 .users
284 .set_timezone(&body.user_id, profile_str(&response, "m.tz"));
285
286 let value = response.get(body.field.as_str()).ok_or_else(|| {
287 err!(Request(NotFound("The requested profile key does not exist.")))
288 })?;
289
290 services
291 .users
292 .set_profile_key(&body.user_id, body.field.as_str(), Some(value));
293
294 let profile_key_value = ProfileFieldValue::new(body.field.as_str(), value.clone())?;
295
296 return Ok(get_profile_field::v3::Response { value: Some(profile_key_value) });
297 }
298 }
299
300 if !services.users.exists(&body.user_id).await {
301 return Err!(Request(NotFound("Profile was not found.")));
304 }
305
306 let value = services
307 .users
308 .profile_key(&body.user_id, body.field.as_str())
309 .await
310 .and_then(|val| serde_json::to_value(val.json()).map_err(Into::into))
311 .map_err(|_| err!(Request(NotFound("The requested profile key does not exist."))))?;
312
313 let profile_key_value = ProfileFieldValue::new(body.field.as_str(), value)?;
314
315 Ok(get_profile_field::v3::Response { value: Some(profile_key_value) })
316}