use std::time::Duration; use axum::extract::State; use axum_client_ip::InsecureClientIp; use conduit::{info, utils::ReadyExt, Err}; use rand::Rng; use ruma::{ api::client::{ error::ErrorKind, room::{report_content, report_room}, }, events::room::message, int, EventId, RoomId, UserId, }; use tokio::time::sleep; use crate::{ debug_info, service::{pdu::PduEvent, Services}, Error, Result, Ruma, }; /// # `POST /_matrix/client/v3/rooms/{roomId}/report` /// /// Reports an abusive room to homeserver admins #[tracing::instrument(skip_all, fields(%client), name = "report_room")] pub(crate) async fn report_room_route( State(services): State, InsecureClientIp(client): InsecureClientIp, body: Ruma, ) -> Result { // user authentication let sender_user = body.sender_user.as_ref().expect("user is authenticated"); info!( "Received room report by user {sender_user} for room {} with reason: \"{}\"", body.room_id, body.reason.as_deref().unwrap_or("") ); if body.reason.as_ref().is_some_and(|s| s.len() > 750) { return Err(Error::BadRequest( ErrorKind::InvalidParam, "Reason too long, should be 750 characters or fewer", )); }; delay_response().await; if !services .rooms .state_cache .server_in_room(&services.globals.config.server_name, &body.room_id) .await { return Err!(Request(NotFound( "Room does not exist to us, no local users have joined at all" ))); } // send admin room message that we received the report with an @room ping for // urgency services .admin .send_message(message::RoomMessageEventContent::text_markdown(format!( "@room Room report received from {} -\n\nRoom ID: {}\n\nReport Reason: {}", sender_user.to_owned(), body.room_id, body.reason.as_deref().unwrap_or("") ))) .await .ok(); Ok(report_room::v3::Response {}) } /// # `POST /_matrix/client/v3/rooms/{roomId}/report/{eventId}` /// /// Reports an inappropriate event to homeserver admins #[tracing::instrument(skip_all, fields(%client), name = "report_event")] pub(crate) async fn report_event_route( State(services): State, InsecureClientIp(client): InsecureClientIp, body: Ruma, ) -> Result { // user authentication let sender_user = body.sender_user.as_ref().expect("user is authenticated"); info!( "Received event report by user {sender_user} for room {} and event ID {}, with reason: \"{}\"", body.room_id, body.event_id, body.reason.as_deref().unwrap_or("") ); delay_response().await; // check if we know about the reported event ID or if it's invalid let Ok(pdu) = services.rooms.timeline.get_pdu(&body.event_id).await else { return Err!(Request(NotFound("Event ID is not known to us or Event ID is invalid"))); }; is_event_report_valid( &services, &pdu.event_id, &body.room_id, sender_user, body.reason.as_ref(), body.score, &pdu, ) .await?; // send admin room message that we received the report with an @room ping for // urgency services .admin .send_message(message::RoomMessageEventContent::text_markdown(format!( "@room Event report received from {} -\n\nEvent ID: {}\nRoom ID: {}\nSent By: {}\n\nReport Score: \ {}\nReport Reason: {}", sender_user.to_owned(), pdu.event_id, pdu.room_id, pdu.sender, body.score.unwrap_or_else(|| ruma::Int::from(0)), body.reason.as_deref().unwrap_or("") ))) .await .ok(); Ok(report_content::v3::Response {}) } /// in the following order: /// /// check if the room ID from the URI matches the PDU's room ID /// check if score is in valid range /// check if report reasoning is less than or equal to 750 characters /// check if reporting user is in the reporting room async fn is_event_report_valid( services: &Services, event_id: &EventId, room_id: &RoomId, sender_user: &UserId, reason: Option<&String>, score: Option, pdu: &std::sync::Arc, ) -> Result<()> { debug_info!("Checking if report from user {sender_user} for event {event_id} in room {room_id} is valid"); if room_id != pdu.room_id { return Err(Error::BadRequest( ErrorKind::NotFound, "Event ID does not belong to the reported room", )); } if score.is_some_and(|s| s > int!(0) || s < int!(-100)) { return Err(Error::BadRequest( ErrorKind::InvalidParam, "Invalid score, must be within 0 to -100", )); }; if reason.as_ref().is_some_and(|s| s.len() > 750) { return Err(Error::BadRequest( ErrorKind::InvalidParam, "Reason too long, should be 750 characters or fewer", )); }; if !services .rooms .state_cache .room_members(room_id) .ready_any(|user_id| user_id == sender_user) .await { return Err(Error::BadRequest( ErrorKind::NotFound, "You are not in the room you are reporting.", )); } Ok(()) } /// even though this is kinda security by obscurity, let's still make a small /// random delay sending a response per spec suggestion regarding /// enumerating for potential events existing in our server. async fn delay_response() { let time_to_wait = rand::thread_rng().gen_range(2..5); debug_info!("Got successful /report request, waiting {time_to_wait} seconds before sending successful response."); sleep(Duration::from_secs(time_to_wait)).await; }