Skip to main content

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