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;
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 let mut errors = 0_usize;
35 while let Err(e) = services
36 .membership
37 .join(Join {
38 sender_user,
39 room_id,
40 orig_room_id: None,
41 reason: body.reason.clone(),
42 servers: &[],
43 is_appservice: body.appservice_info.is_some(),
44 extra_content: extra_content.clone(),
45 })
46 .await
47 {
48 errors = errors.saturating_add(1);
49 if errors >= services.config.max_join_attempts_per_join_request {
50 warn!(
51 "Several servers failed. Giving up for this request. Try again for different \
52 server selection."
53 );
54 return Err(e);
55 }
56 }
57
58 Ok(join_room_by_id::v3::Response { room_id: room_id.to_owned() })
59}
60
61#[tracing::instrument(skip_all, fields(%client), name = "join")]
71pub(crate) async fn join_room_by_id_or_alias_route(
72 State(services): State<crate::State>,
73 ClientIp(client): ClientIp,
74 body: Ruma<join_room_by_id_or_alias::v3::Request>,
75) -> Result<join_room_by_id_or_alias::v3::Response> {
76 let sender_user = body.sender_user();
77 let appservice_info = &body.appservice_info;
78
79 let (room_id, servers) = services
80 .alias
81 .maybe_resolve_with_servers(&body.room_id_or_alias, Some(&body.via))
82 .await?;
83
84 banned_room_check(&services, sender_user, &room_id, Some(&body.room_id_or_alias), client)
85 .await?;
86
87 let extra_content = extra_member_content(body.json_body.as_ref());
88
89 let mut errors = 0_usize;
90 while let Err(e) = services
91 .membership
92 .join(Join {
93 sender_user,
94 room_id: &room_id,
95 orig_room_id: Some(&body.room_id_or_alias),
96 reason: body.reason.clone(),
97 servers: &servers,
98 is_appservice: appservice_info.is_some(),
99 extra_content: extra_content.clone(),
100 })
101 .await
102 {
103 errors = errors.saturating_add(1);
104 if errors >= services.config.max_join_attempts_per_join_request {
105 warn!(
106 "Several servers failed. Giving up for this request. Try again for different \
107 server selection."
108 );
109 return Err(e);
110 }
111 }
112
113 Ok(join_room_by_id_or_alias::v3::Response { room_id: room_id.clone() })
114}
115
116const RESERVED_JOIN_KEYS: [&str; 3] =
117 ["reason", "third_party_signed", "join_authorised_via_users_server"];
118
119fn extra_member_content(json_body: Option<&CanonicalJsonValue>) -> Option<CanonicalJsonObject> {
121 let CanonicalJsonValue::Object(object) = json_body? else {
122 return None;
123 };
124
125 let extra: CanonicalJsonObject = object
126 .iter()
127 .filter(|(key, _)| !RESERVED_JOIN_KEYS.contains(&key.as_str()))
128 .map(|(key, value)| (key.clone(), value.clone()))
129 .collect();
130
131 (!extra.is_empty()).then_some(extra)
132}