tuwunel_api/server/
make_join.rs1use axum::extract::State;
2use futures::{StreamExt, TryFutureExt, pin_mut};
3use ruma::{
4 OwnedUserId, RoomId, UserId,
5 api::{
6 error::{ErrorKind, IncompatibleRoomVersionErrorData},
7 federation::membership::prepare_join_event,
8 },
9 events::{
10 StateEventType,
11 room::{
12 join_rules::{AllowRule, JoinRule, RoomJoinRulesEventContent},
13 member::{MembershipState, RoomMemberEventContent},
14 },
15 },
16};
17use tuwunel_core::{
18 Err, Error, Result, at, debug_info,
19 matrix::{RoomVersionRules, pdu::PduBuilder, room_version},
20 utils::IterStream,
21};
22use tuwunel_service::Services;
23
24use super::utils::require_known_room;
25use crate::Ruma;
26
27pub(crate) async fn create_join_event_template_route(
31 State(services): State<crate::State>,
32 body: Ruma<prepare_join_event::v1::Request>,
33) -> Result<prepare_join_event::v1::Response> {
34 require_known_room(&services, &body.room_id, body.origin()).await?;
35
36 if body.user_id.server_name() != body.origin() {
37 return Err!(Request(BadJson("Not allowed to join on behalf of another server/user.")));
38 }
39
40 if let Some(server) = body.room_id.server_name()
41 && services
42 .config
43 .is_forbidden_remote_server_name(server)
44 {
45 return Err!(Request(Forbidden(warn!(
46 "Room ID server name {server} is banned on this homeserver."
47 ))));
48 }
49
50 let room_version_id = services
51 .state
52 .get_room_version(&body.room_id)
53 .await?;
54
55 if !body.ver.contains(&room_version_id) {
56 return Err(Error::BadRequest(
57 ErrorKind::IncompatibleRoomVersion(IncompatibleRoomVersionErrorData::new(
58 room_version_id,
59 )),
60 "Room version not supported.",
61 ));
62 }
63
64 let room_version_rules = room_version::rules(&room_version_id)?;
65
66 let state_lock = services.state.mutex.lock(&body.room_id).await;
67
68 let join_authorized_via_users_server: Option<OwnedUserId> =
69 if user_can_perform_restricted_join(
70 &services,
71 &body.user_id,
72 &body.room_id,
73 &room_version_rules,
74 )
75 .await?
76 {
77 let users = services
78 .state_cache
79 .local_users_in_room(&body.room_id)
80 .filter(|user| {
81 services.state_accessor.user_can_invite(
82 &body.room_id,
83 user,
84 &body.user_id,
85 &state_lock,
86 )
87 })
88 .map(ToOwned::to_owned);
89
90 pin_mut!(users);
91 let Some(auth_user) = users.next().await else {
92 return Err!(Request(UnableToGrantJoin(
93 "No user on this server is able to assist in joining."
94 )));
95 };
96
97 Some(auth_user)
98 } else {
99 None
100 };
101
102 let pdu_json = services
103 .timeline
104 .create_hash_and_sign_event(
105 PduBuilder::state(body.user_id.to_string(), &RoomMemberEventContent {
106 join_authorized_via_users_server,
107 ..RoomMemberEventContent::new(MembershipState::Join)
108 }),
109 &body.user_id,
110 &body.room_id,
111 &state_lock,
112 )
113 .map_ok(at!(1))
114 .await?;
115
116 drop(state_lock);
117
118 Ok(prepare_join_event::v1::Response {
119 room_version: Some(room_version_id.clone()),
120 event: services
121 .federation
122 .format_pdu_into(pdu_json, Some(&room_version_id))
123 .await,
124 })
125}
126
127pub(crate) async fn user_can_perform_restricted_join(
129 services: &Services,
130 user_id: &UserId,
131 room_id: &RoomId,
132 room_version_rules: &RoomVersionRules,
133) -> Result<bool> {
134 if !room_version_rules
135 .authorization
136 .restricted_join_rule
137 {
138 return Ok(false);
139 }
140
141 if services
142 .state_cache
143 .is_joined(user_id, room_id)
144 .await
145 {
146 return Ok(false);
148 }
149
150 if services
151 .state_cache
152 .is_invited(user_id, room_id)
153 .await
154 {
155 return Ok(false);
156 }
157
158 let Ok(join_rules_event_content) = services
159 .state_accessor
160 .room_state_get_content::<RoomJoinRulesEventContent>(
161 room_id,
162 &StateEventType::RoomJoinRules,
163 "",
164 )
165 .await
166 else {
167 return Ok(false);
168 };
169
170 let (JoinRule::Restricted(r) | JoinRule::KnockRestricted(r)) =
171 join_rules_event_content.join_rule
172 else {
173 return Ok(false);
174 };
175
176 if r.allow.is_empty() {
177 debug_info!("{room_id} is restricted but the allow key is empty");
178 return Ok(false);
179 }
180
181 if r.allow
182 .iter()
183 .filter_map(|rule| {
184 if let AllowRule::RoomMembership(membership) = rule {
185 Some(membership)
186 } else {
187 None
188 }
189 })
190 .stream()
191 .any(|m| {
192 services
193 .state_cache
194 .is_joined(user_id, &m.room_id)
195 })
196 .await
197 {
198 Ok(true)
199 } else {
200 Err!(Request(UnableToAuthorizeJoin(
201 "Joining user is not known to be in any required room."
202 )))
203 }
204}