tuwunel_api_client/membership/
join.rs1use axum::extract::State;
2use ruma::{
3 CanonicalJsonObject, CanonicalJsonValue, RoomId,
4 api::client::membership::{join_room_by_id, join_room_by_id_or_alias},
5};
6use tuwunel_core::{Result, warn};
7use tuwunel_service::membership::{Join, is_reserved_join_key};
8
9use super::banned_room_check;
10use crate::{ClientIp, Ruma};
11
12#[tracing::instrument(skip_all, fields(%client), name = "join")]
21pub(crate) async fn join_room_by_id_route(
22 State(services): State<crate::State>,
23 ClientIp(client): ClientIp,
24 body: Ruma<join_room_by_id::v3::Request>,
25) -> Result<join_room_by_id::v3::Response> {
26 let sender_user = body.sender_user();
27
28 let room_id: &RoomId = &body.room_id;
29
30 banned_room_check(&services, sender_user, room_id, None, client).await?;
31
32 let extra_content = extra_member_content(body.json_body.as_ref());
33
34 services
35 .membership
36 .check_extra_content(sender_user, room_id, extra_content.as_ref())
37 .await?;
38
39 let mut errors = 0_usize;
40 while let Err(e) = services
41 .membership
42 .join(Join {
43 sender_user,
44 room_id,
45 orig_room_id: None,
46 reason: body.reason.clone(),
47 servers: &[],
48 is_appservice: body.appservice_info.is_some(),
49 extra_content: extra_content.clone(),
50 })
51 .await
52 {
53 errors = errors.saturating_add(1);
54 if services
55 .config
56 .rooms
57 .joins
58 .max_attempts_per_request
59 .le(&errors)
60 {
61 warn!(
62 "Several servers failed. Giving up for this request. Try again for different \
63 server selection."
64 );
65 return Err(e);
66 }
67 }
68
69 Ok(join_room_by_id::v3::Response { room_id: room_id.to_owned() })
70}
71
72#[tracing::instrument(skip_all, fields(%client), name = "join")]
82pub(crate) async fn join_room_by_id_or_alias_route(
83 State(services): State<crate::State>,
84 ClientIp(client): ClientIp,
85 body: Ruma<join_room_by_id_or_alias::v3::Request>,
86) -> Result<join_room_by_id_or_alias::v3::Response> {
87 let sender_user = body.sender_user();
88 let appservice_info = &body.appservice_info;
89
90 let (room_id, servers) = services
91 .alias
92 .maybe_resolve_with_servers(&body.room_id_or_alias, Some(&body.via))
93 .await?;
94
95 banned_room_check(&services, sender_user, &room_id, Some(&body.room_id_or_alias), client)
96 .await?;
97
98 let extra_content = extra_member_content(body.json_body.as_ref());
99
100 services
101 .membership
102 .check_extra_content(sender_user, &room_id, extra_content.as_ref())
103 .await?;
104
105 let mut errors = 0_usize;
106 while let Err(e) = services
107 .membership
108 .join(Join {
109 sender_user,
110 room_id: &room_id,
111 orig_room_id: Some(&body.room_id_or_alias),
112 reason: body.reason.clone(),
113 servers: &servers,
114 is_appservice: appservice_info.is_some(),
115 extra_content: extra_content.clone(),
116 })
117 .await
118 {
119 errors = errors.saturating_add(1);
120 if services
121 .config
122 .rooms
123 .joins
124 .max_attempts_per_request
125 .le(&errors)
126 {
127 warn!(
128 "Several servers failed. Giving up for this request. Try again for different \
129 server selection."
130 );
131 return Err(e);
132 }
133 }
134
135 Ok(join_room_by_id_or_alias::v3::Response { room_id: room_id.clone() })
136}
137
138fn extra_member_content(json_body: Option<&CanonicalJsonValue>) -> Option<CanonicalJsonObject> {
140 let CanonicalJsonValue::Object(object) = json_body? else {
141 return None;
142 };
143
144 let extra: CanonicalJsonObject = object
145 .iter()
146 .filter(|(key, _)| !is_reserved_join_key(key))
147 .map(|(key, value)| (key.clone(), value.clone()))
148 .collect();
149
150 (!extra.is_empty()).then_some(extra)
151}