tuwunel_api/server/
send_knock.rs1use axum::extract::State;
2use futures::{FutureExt, future::try_join};
3use ruma::{
4 OwnedServerName, OwnedUserId,
5 RoomVersionId::*,
6 api::federation::membership::create_knock_event,
7 events::{
8 StateEventType,
9 room::member::{MembershipState, RoomMemberEventContent},
10 },
11 serde::JsonObject,
12};
13use tuwunel_core::{
14 Err, Result, at, err,
15 matrix::{event::gen_event_id_canonical_json, pdu::PduEvent},
16 warn,
17};
18
19use crate::Ruma;
20
21pub(crate) async fn create_knock_event_v1_route(
25 State(services): State<crate::State>,
26 body: Ruma<create_knock_event::v1::Request>,
27) -> Result<create_knock_event::v1::Response> {
28 if let Some(server) = body.room_id.server_name()
29 && services
30 .config
31 .is_forbidden_remote_server_name(server)
32 {
33 warn!(
34 "Server {} tried knocking room ID {} which has a server name that is globally \
35 forbidden. Rejecting.",
36 body.origin(),
37 &body.room_id,
38 );
39 return Err!(Request(Forbidden("Server is banned on this homeserver.")));
40 }
41
42 if !services.metadata.exists(&body.room_id).await {
43 return Err!(Request(NotFound("Room is unknown to this server.")));
44 }
45
46 services
48 .event_handler
49 .acl_check(body.origin(), &body.room_id)
50 .await?;
51
52 let room_version_id = services
53 .state
54 .get_room_version(&body.room_id)
55 .await?;
56
57 if matches!(room_version_id, V1 | V2 | V3 | V4 | V5 | V6) {
58 return Err!(Request(Forbidden("Room version does not support knocking.")));
59 }
60
61 let Ok((event_id, value)) = gen_event_id_canonical_json(&body.pdu, &room_version_id) else {
62 return Err!(Request(InvalidParam("Could not convert event to canonical json.")));
64 };
65
66 let event_type: StateEventType = serde_json::from_value(
67 value
68 .get("type")
69 .ok_or_else(|| err!(Request(InvalidParam("Event has no event type."))))?
70 .clone()
71 .into(),
72 )
73 .map_err(|e| err!(Request(InvalidParam("Event has invalid event type: {e}"))))?;
74
75 if event_type != StateEventType::RoomMember {
76 return Err!(Request(InvalidParam(
77 "Not allowed to send non-membership state event to knock endpoint.",
78 )));
79 }
80
81 let content: RoomMemberEventContent = serde_json::from_value(
82 value
83 .get("content")
84 .ok_or_else(|| err!(Request(InvalidParam("Membership event has no content"))))?
85 .clone()
86 .into(),
87 )
88 .map_err(|e| err!(Request(InvalidParam("Event has invalid membership content: {e}"))))?;
89
90 if content.membership != MembershipState::Knock {
91 return Err!(Request(InvalidParam(
92 "Not allowed to send a non-knock membership event to knock endpoint."
93 )));
94 }
95
96 let sender: OwnedUserId = serde_json::from_value(
98 value
99 .get("sender")
100 .ok_or_else(|| err!(Request(InvalidParam("Event has no sender user ID."))))?
101 .clone()
102 .into(),
103 )
104 .map_err(|e| err!(Request(BadJson("Event sender is not a valid user ID: {e}"))))?;
105
106 services
107 .event_handler
108 .acl_check(sender.server_name(), &body.room_id)
109 .await?;
110
111 if sender.server_name() != body.origin() {
113 return Err!(Request(BadJson("Not allowed to knock on behalf of another server/user.")));
114 }
115
116 let state_key: OwnedUserId = serde_json::from_value(
117 value
118 .get("state_key")
119 .ok_or_else(|| err!(Request(InvalidParam("Event does not have a state_key"))))?
120 .clone()
121 .into(),
122 )
123 .map_err(|e| err!(Request(BadJson("Event does not have a valid state_key: {e}"))))?;
124
125 if state_key != sender {
126 return Err!(Request(InvalidParam("state_key does not match sender user of event.")));
127 }
128
129 let origin: OwnedServerName = serde_json::from_value(
130 value
131 .get("origin")
132 .ok_or_else(|| err!(Request(BadJson("Event does not have an origin server name."))))?
133 .clone()
134 .into(),
135 )
136 .map_err(|e| err!(Request(BadJson("Event has an invalid origin server name: {e}"))))?;
137
138 let mut event: JsonObject = serde_json::from_str(body.pdu.get())
139 .map_err(|e| err!(Request(InvalidParam("Invalid knock event PDU: {e}"))))?;
140
141 event.insert("event_id".to_owned(), "$placeholder".into());
142
143 let pdu: PduEvent = serde_json::from_value(event.into())
144 .map_err(|e| err!(Request(InvalidParam("Invalid knock event PDU: {e}"))))?;
145
146 let mutex_lock = services
147 .event_handler
148 .mutex_federation
149 .lock(&body.room_id)
150 .await;
151
152 let pdu_id = services
153 .event_handler
154 .handle_incoming_pdu(&origin, &body.room_id, &event_id, value.clone(), true)
155 .boxed()
156 .await?
157 .map(at!(0))
158 .ok_or_else(|| err!(Request(InvalidParam("Could not accept as timeline event."))))?;
159
160 drop(mutex_lock);
161
162 let broadcast = services
163 .sending
164 .send_pdu_room(&body.room_id, &pdu_id);
165
166 let knock_room_state = services.state.summary_stripped(&pdu).map(Ok);
167
168 let (knock_room_state, ()) = try_join(knock_room_state, broadcast).await?;
169
170 Ok(create_knock_event::v1::Response {
171 knock_room_state: knock_room_state
172 .into_iter()
173 .map(Into::into)
174 .collect(),
175 })
176}