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 let Ok(membership) = services
67		.state_accessor
68		.get_member(&body.room_id, &body.user_id)
69		.await && membership.membership == MembershipState::Ban
70	{
71		debug_warn!(
72			"Remote user {} is banned from {} but attempted to knock",
73			&body.user_id,
74			&body.room_id
75		);
76
77		return Err!(Request(Forbidden("You cannot knock on a room you are banned from.")));
78	}
79
80	let pdu_json = services
81		.timeline
82		.create_hash_and_sign_event(
83			PduBuilder::state(
84				body.user_id.to_string(),
85				&RoomMemberEventContent::new(MembershipState::Knock),
86			),
87			&body.user_id,
88			&body.room_id,
89			&state_lock,
90		)
91		.map_ok(at!(1))
92		.await?;
93
94	drop(state_lock);
95
96	let event = services
97		.federation
98		.format_pdu_into(pdu_json, Some(&room_version_id))
99		.await;
100
101	// room v3 and above removed the "event_id" field from remote PDU format
102	Ok(prepare_knock_event::v1::Response { room_version: room_version_id, event })
103}