Skip to main content

tuwunel_api_federation/
make_knock.rs

1use axum::extract::State;
2use futures::TryFutureExt;
3use ruma::{
4	api::{
5		error::{ErrorKind, IncompatibleRoomVersionErrorData},
6		federation::membership::prepare_knock_event,
7	},
8	events::room::member::{MembershipState, RoomMemberEventContent},
9};
10use tuwunel_core::{Err, Error, Result, at, debug_warn};
11use tuwunel_matrix::{pdu::PduBuilder, room_version};
12
13use super::utils::require_known_room;
14use crate::Ruma;
15
16/// # `GET /_matrix/federation/v1/make_knock/{roomId}/{userId}`
17///
18/// Creates a knock template.
19pub(crate) async fn create_knock_event_template_route(
20	State(services): State<crate::State>,
21	body: Ruma<prepare_knock_event::v1::Request>,
22) -> Result<prepare_knock_event::v1::Response> {
23	require_known_room(&services, &body.room_id, body.origin()).await?;
24
25	if body.user_id.server_name() != body.origin() {
26		return Err!(Request(BadJson("Not allowed to knock on behalf of another server/user.")));
27	}
28
29	if let Some(server) = body.room_id.server_name()
30		&& services
31			.config
32			.is_forbidden_remote_server_name(server)
33	{
34		return Err!(Request(Forbidden("Server is banned on this homeserver.")));
35	}
36
37	let room_version_id = services
38		.state
39		.get_room_version(&body.room_id)
40		.await?;
41
42	if !body.ver.contains(&room_version_id) {
43		return Err(Error::BadRequest(
44			ErrorKind::IncompatibleRoomVersion(IncompatibleRoomVersionErrorData::new(
45				room_version_id,
46			)),
47			"Your homeserver does not support the features required to knock on this room.",
48		));
49	}
50
51	let room_version_rules = room_version::rules(&room_version_id)?;
52
53	if !room_version_rules.authorization.knocking {
54		return Err(Error::BadRequest(
55			ErrorKind::IncompatibleRoomVersion(IncompatibleRoomVersionErrorData::new(
56				room_version_id,
57			)),
58			"Room version does not support knocking.",
59		));
60	}
61
62	let state_lock = services.state.mutex.lock(&body.room_id).await;
63
64	if services
65		.state_accessor
66		.get_member(&body.room_id, &body.user_id)
67		.await
68		.is_ok_and(|content| content.membership == MembershipState::Ban)
69	{
70		debug_warn!(
71			user_id = %body.user_id,
72			room_id = %body.room_id,
73			"Banned remote user attempted to knock."
74		);
75
76		return Err!(Request(Forbidden("You cannot knock on a room you are banned from.")));
77	}
78
79	let pdu_json = services
80		.timeline
81		.create_hash_and_sign_event(
82			PduBuilder::state(
83				body.user_id.to_string(),
84				&RoomMemberEventContent::new(MembershipState::Knock),
85			),
86			&body.user_id,
87			&body.room_id,
88			&state_lock,
89		)
90		.map_ok(at!(1))
91		.await?;
92
93	drop(state_lock);
94
95	let event = services
96		.federation
97		.format_pdu_into(pdu_json, Some(&room_version_id))
98		.await;
99
100	// room v3 and above removed the "event_id" field from remote PDU format
101	Ok(prepare_knock_event::v1::Response { room_version: room_version_id, event })
102}