Skip to main content

tuwunel_api/client/report/
report_event.rs

1use axum::extract::State;
2use ruma::{EventId, RoomId, UserId, api::client::room::report_content};
3use tuwunel_core::{Err, Result, debug_info, info, matrix::pdu::PduEvent, utils::ReadyExt};
4use tuwunel_service::Services;
5
6use super::REASON_MAX_LEN;
7use crate::{ClientIp, Ruma};
8
9/// # `POST /_matrix/client/v3/rooms/{roomId}/report/{eventId}`
10///
11/// Reports an inappropriate event to homeserver admins
12#[tracing::instrument(skip_all, fields(%client), name = "report_event")]
13pub(crate) async fn report_event_route(
14	State(services): State<crate::State>,
15	ClientIp(client): ClientIp,
16	body: Ruma<report_content::v3::Request>,
17) -> Result<report_content::v3::Response> {
18	let sender_user = body.sender_user();
19	let reason = body.reason.as_deref().unwrap_or("");
20
21	info!(
22		"Received event report by user {sender_user} for room {} and event ID {}, with reason: \
23		 \"{}\"",
24		body.room_id, body.event_id, reason,
25	);
26
27	// check if we know about the reported event ID or if it's invalid
28	let Ok(pdu) = services.timeline.get_pdu(&body.event_id).await else {
29		return Err!(Request(NotFound("Event ID is not known to us or Event ID is invalid")));
30	};
31
32	is_event_report_valid(
33		&services,
34		&pdu.event_id,
35		&body.room_id,
36		sender_user,
37		body.reason.as_ref(),
38		&pdu,
39	)
40	.await?;
41
42	services
43		.admin
44		.send_report(&format!(
45			"@room Event report received from {}\nReport Reason: {}\n\nEvent ID: {}\nRoom ID: \
46			 {}\nSent By: {}",
47			sender_user, reason, pdu.event_id, pdu.room_id, pdu.sender,
48		))
49		.await;
50
51	Ok(report_content::v3::Response {})
52}
53
54/// in the following order:
55///
56/// check if the room ID from the URI matches the PDU's room ID
57/// check if report reasoning is less than or equal to 750 characters
58/// check if reporting user is in the reporting room
59async fn is_event_report_valid(
60	services: &Services,
61	event_id: &EventId,
62	room_id: &RoomId,
63	sender_user: &UserId,
64	reason: Option<&String>,
65	pdu: &PduEvent,
66) -> Result {
67	debug_info!(
68		"Checking if report from user {sender_user} for event {event_id} in room {room_id} is \
69		 valid"
70	);
71
72	if room_id != pdu.room_id {
73		return Err!(Request(NotFound("Event ID does not belong to the reported room",)));
74	}
75
76	if reason
77		.as_ref()
78		.is_some_and(|s| s.len() > REASON_MAX_LEN)
79	{
80		return Err!(Request(InvalidParam(
81			"Reason too long, should be {REASON_MAX_LEN} characters or fewer"
82		)));
83	}
84
85	if !services
86		.state_cache
87		.room_members(room_id)
88		.ready_any(|user_id| user_id == sender_user)
89		.await
90	{
91		return Err!(Request(NotFound("You are not in the room you are reporting.",)));
92	}
93
94	Ok(())
95}