Skip to main content

tuwunel_service/rooms/timeline/
redact.rs

1//! Replaces accepted timeline events with their room-version redacted form.
2//!
3//! Redaction can retain the original JSON for operators and removes searchable
4//! or relational content before overwriting the accepted row. These side
5//! effects are coordinated under the caller's room timeline guard.
6
7use ruma::{
8	EventId, RoomId,
9	canonical_json::{RedactedBecause, redact_in_place},
10};
11use tuwunel_core::{Result, err, implement, matrix::event::Event};
12
13use crate::rooms::{short::ShortRoomId, timeline::RoomMutexGuard};
14
15/// Replaces an accepted PDU with its room-version redacted form.
16///
17/// Failure to resolve the event's accepted PDU ID is treated as a successful
18/// no-op. Original retention, search removal, and relation deletion occur
19/// before the accepted row is replaced, so the operation is not atomic if a
20/// later step fails.
21#[implement(super::Service)]
22#[tracing::instrument(name = "redact", level = "debug", skip(self))]
23pub async fn redact_pdu<Pdu: Event + Send + Sync>(
24	&self,
25	event_id: &EventId,
26	reason: &Pdu,
27	shortroomid: ShortRoomId,
28	state_lock: &RoomMutexGuard,
29) -> Result {
30	let Ok(pdu_id) = self.get_pdu_id(event_id).await else {
31		// If event does not exist, just noop
32		// TODO this is actually wrong!
33		return Ok(());
34	};
35
36	let mut pdu = self
37		.get_pdu_json_from_id(&pdu_id)
38		.await
39		.map_err(|e| {
40			err!(Database(error!(?pdu_id, ?event_id, ?e, "PDU ID points to invalid PDU.")))
41		})?;
42
43	self.services
44		.retention
45		.save_original_pdu(event_id, &pdu, state_lock)
46		.await;
47
48	let body = pdu["content"]
49		.as_object()
50		.and_then(|obj| obj.get("body"))
51		.and_then(|body| body.as_str());
52
53	if let Some(body) = body {
54		self.services
55			.search
56			.deindex_pdu(shortroomid, &pdu_id, body);
57	}
58
59	let room_id: &RoomId = pdu.get("room_id").try_into()?;
60
61	let room_version_id = self
62		.services
63		.state
64		.get_room_version(room_id)
65		.await?;
66
67	let room_version_rules = room_version_id.rules().ok_or_else(|| {
68		err!(Request(UnsupportedRoomVersion(
69			"Cannot redact event for unknown room version {room_version_id:?}."
70		)))
71	})?;
72
73	self.services
74		.pdu_metadata
75		.delete_typed_relation(&pdu_id, &pdu)
76		.await;
77
78	redact_in_place(
79		&mut pdu,
80		&room_version_rules.redaction,
81		Some(RedactedBecause::from_json(reason.to_canonical_object())),
82	)
83	.map_err(|err| err!("invalid event: {err}"))?;
84
85	self.replace_pdu(&pdu_id, &pdu).await
86}