tuwunel_service/membership/
invite.rs1use futures::FutureExt;
2use ruma::{
3 OwnedServerName, RoomId, UserId,
4 api::{
5 error::ErrorKind,
6 federation::membership::{RawStrippedState, create_invite},
7 },
8 events::room::member::{MembershipState, RoomMemberEventContent},
9};
10use tuwunel_core::{
11 Err, Result, at, err, implement, matrix::event::gen_event_id_canonical_json, pdu::PduBuilder,
12};
13
14use super::Service;
15
16#[implement(Service)]
17#[tracing::instrument(
18 level = "debug",
19 skip_all,
20 fields(%sender_user, %room_id, %user_id)
21)]
22pub async fn invite(
23 &self,
24 sender_user: &UserId,
25 user_id: &UserId,
26 room_id: &RoomId,
27 reason: Option<&String>,
28 is_direct: bool,
29) -> Result {
30 if self.services.globals.user_is_local(user_id) {
31 self.local_invite(sender_user, user_id, room_id, reason, is_direct)
32 .boxed()
33 .await?;
34 } else {
35 self.remote_invite(sender_user, user_id, room_id, reason, is_direct)
36 .boxed()
37 .await?;
38 }
39
40 Ok(())
41}
42
43#[implement(Service)]
44#[tracing::instrument(name = "remote", level = "debug", skip_all)]
45async fn remote_invite(
46 &self,
47 sender_user: &UserId,
48 user_id: &UserId,
49 room_id: &RoomId,
50 reason: Option<&String>,
51 is_direct: bool,
52) -> Result {
53 let (pdu, pdu_json, invite_room_state, room_version_id) = {
54 let state_lock = self.services.state.mutex.lock(room_id).await;
55
56 let mut content = RoomMemberEventContent {
57 is_direct: Some(is_direct),
58 reason: reason.cloned(),
59 ..RoomMemberEventContent::new(MembershipState::Invite)
60 };
61
62 self.services
63 .profile
64 .fill_profile_data(user_id, &mut content)
65 .await;
66
67 let (pdu, pdu_json) = self
68 .services
69 .timeline
70 .create_hash_and_sign_event(
71 PduBuilder::state(user_id.to_string(), &content),
72 sender_user,
73 room_id,
74 &state_lock,
75 )
76 .await?;
77
78 let room_version_id = self
79 .services
80 .state
81 .get_room_version(room_id)
82 .await?;
83
84 let invite_room_state = self
85 .services
86 .state
87 .summary_pdus(&pdu, &pdu_json, &room_version_id)
88 .await;
89
90 drop(state_lock);
91
92 (pdu, pdu_json, invite_room_state, room_version_id)
93 };
94
95 let response = self
96 .services
97 .federation
98 .execute(user_id.server_name(), create_invite::v2::Request {
99 room_id: room_id.to_owned(),
100 event_id: (*pdu.event_id).to_owned(),
101 room_version: room_version_id.clone(),
102 event: self
103 .services
104 .federation
105 .format_pdu_into(pdu_json.clone(), Some(&room_version_id))
106 .await,
107 invite_room_state: invite_room_state
108 .into_iter()
109 .map(RawStrippedState::Pdu)
110 .collect(),
111 via: self
112 .services
113 .state_cache
114 .servers_route_via(room_id)
115 .await
116 .ok(),
117 })
118 .await
119 .map_err(|e| match e.kind() {
120 | ErrorKind::IncompatibleRoomVersion { .. } | ErrorKind::UnsupportedRoomVersion =>
121 err!(Request(UnsupportedRoomVersion(
122 "Server {} does not support room version {room_version_id}.",
123 user_id.server_name(),
124 ))),
125 | ErrorKind::MissingParam => err!(BadServerResponse(
128 "Remote server could not validate the invite's create event."
129 )),
130 | _ => e,
131 })?;
132
133 let (event_id, value) = gen_event_id_canonical_json(&response.event, &room_version_id)
136 .map_err(|e| {
137 err!(Request(BadJson(warn!("Could not convert event to canonical JSON: {e}"))))
138 })?;
139
140 if pdu.event_id != event_id {
141 return Err!(Request(BadJson(warn!(
142 %pdu.event_id, %event_id,
143 "Server {} sent event with wrong event ID",
144 user_id.server_name()
145 ))));
146 }
147
148 let origin: OwnedServerName = serde_json::from_value(serde_json::to_value(
149 value
150 .get("origin")
151 .ok_or_else(|| err!(Request(BadJson("Event missing origin field."))))?,
152 )?)
153 .map_err(|e| {
154 err!(Request(BadJson(warn!("Origin field in event is not a valid server name: {e}"))))
155 })?;
156
157 let pdu_id = self
158 .services
159 .event_handler
160 .handle_incoming_pdu(&origin, room_id, &event_id, value, true)
161 .await?
162 .map(at!(0))
163 .ok_or_else(|| {
164 err!(Request(InvalidParam("Could not accept incoming PDU as timeline event.")))
165 })?;
166
167 self.services
168 .sending
169 .send_pdu_room(room_id, &pdu_id)
170 .await?;
171
172 Ok(())
173}
174
175#[implement(Service)]
176#[tracing::instrument(name = "local", level = "debug", skip_all)]
177async fn local_invite(
178 &self,
179 sender_user: &UserId,
180 user_id: &UserId,
181 room_id: &RoomId,
182 reason: Option<&String>,
183 is_direct: bool,
184) -> Result {
185 if self.services.users.invites_blocked(user_id).await {
186 return Err!(Request(InviteBlocked("{user_id} has blocked invites.")));
187 }
188
189 if !self
190 .services
191 .state_cache
192 .is_joined(sender_user, room_id)
193 .await
194 {
195 return Err!(Request(Forbidden(
196 "You must be joined in the room you are trying to invite from."
197 )));
198 }
199
200 let state_lock = self.services.state.mutex.lock(room_id).await;
201
202 let mut content = RoomMemberEventContent {
203 is_direct: Some(is_direct),
204 reason: reason.cloned(),
205 ..RoomMemberEventContent::new(MembershipState::Invite)
206 };
207
208 self.services
209 .profile
210 .fill_profile_data(user_id, &mut content)
211 .await;
212
213 self.services
214 .timeline
215 .build_and_append_pdu(
216 PduBuilder::state(user_id.to_string(), &content),
217 sender_user,
218 room_id,
219 &state_lock,
220 )
221 .await?;
222
223 drop(state_lock);
224
225 Ok(())
226}