Skip to main content

tuwunel_service/deactivate/
mod.rs

1use std::sync::Arc;
2
3use futures::{FutureExt, StreamExt};
4use ruma::{
5	OwnedRoomId, UserId,
6	events::{StateEventType, room::power_levels::RoomPowerLevelsEventContent},
7	profile::ProfileFieldName,
8};
9use tuwunel_core::{Event, Result, info, pdu::PduBuilder, warn};
10
11use crate::profile::Propagation;
12
13pub struct Service {
14	services: Arc<crate::services::OnceServices>,
15}
16
17impl crate::Service for Service {
18	fn build(args: &crate::Args<'_>) -> Result<Arc<Self>> {
19		Ok(Arc::new(Self { services: args.services.clone() }))
20	}
21
22	fn name(&self) -> &str { crate::service::make_name(std::module_path!()) }
23}
24
25impl Service {
26	/// Runs through all the deactivation steps:
27	///
28	/// - Mark as deactivated
29	/// - Removing display name
30	/// - Removing avatar URL and blurhash
31	/// - Removing all profile data
32	/// - Leaving all rooms (and forgets all of them)
33	///
34	/// When `erase` is `true`, additionally erase non-event data per
35	/// MSC4025: all global and per-room account data for the user.
36	pub async fn full_deactivate(&self, user_id: &UserId, erase: bool) -> Result {
37		self.services
38			.users
39			.deactivate_account(user_id)
40			.await?;
41
42		self.services
43			.profile
44			.clear_profile_keys(user_id)
45			.await;
46
47		self.services
48			.profile
49			.update_all_rooms(
50				user_id,
51				&[(ProfileFieldName::DisplayName, None), (ProfileFieldName::AvatarUrl, None)],
52				Propagation::All,
53			)
54			.await;
55
56		let all_joined_rooms: Vec<OwnedRoomId> = self
57			.services
58			.state_cache
59			.rooms_joined(user_id)
60			.map(Into::into)
61			.collect()
62			.await;
63
64		for room_id in all_joined_rooms {
65			let state_lock = self.services.state.mutex.lock(&room_id).await;
66
67			let room_power_levels = self
68				.services
69				.state_accessor
70				.get_power_levels(&room_id)
71				.await
72				.ok();
73
74			let user_can_change_self = room_power_levels
75				.as_ref()
76				.is_some_and(|power_levels| {
77					power_levels.user_can_change_user_power_level(user_id, user_id)
78				});
79
80			let user_can_demote_self = user_can_change_self
81				|| self
82					.services
83					.state_accessor
84					.room_state_get(&room_id, &StateEventType::RoomCreate, "")
85					.await
86					.is_ok_and(|event| event.sender() == user_id);
87
88			if user_can_demote_self {
89				let mut power_levels_content: RoomPowerLevelsEventContent = room_power_levels
90					.map(TryInto::try_into)
91					.transpose()?
92					.unwrap_or_default();
93
94				power_levels_content.users.remove(user_id);
95
96				// ignore errors so deactivation doesn't fail
97				match self
98					.services
99					.timeline
100					.build_and_append_pdu(
101						PduBuilder::state(String::new(), &power_levels_content),
102						user_id,
103						&room_id,
104						&state_lock,
105					)
106					.await
107				{
108					| Err(e) => {
109						warn!(%room_id, %user_id, "Failed to demote user's own power level: {e}");
110					},
111					| _ => {
112						info!("Demoted {user_id} in {room_id} as part of account deactivation");
113					},
114				}
115			}
116		}
117
118		let rooms_joined = self
119			.services
120			.state_cache
121			.rooms_joined(user_id)
122			.map(ToOwned::to_owned);
123
124		let rooms_invited = self
125			.services
126			.state_cache
127			.rooms_invited(user_id)
128			.map(ToOwned::to_owned);
129
130		let rooms_knocked = self
131			.services
132			.state_cache
133			.rooms_knocked(user_id)
134			.map(ToOwned::to_owned);
135
136		let all_rooms: Vec<_> = rooms_joined
137			.chain(rooms_invited)
138			.chain(rooms_knocked)
139			.collect()
140			.await;
141
142		// MSC4025: erase non-event data when the user requested it.
143		if erase {
144			self.services
145				.account_data
146				.erase_user(user_id, None)
147				.await;
148
149			let rooms_left: Vec<OwnedRoomId> = self
150				.services
151				.state_cache
152				.rooms_left(user_id)
153				.map(ToOwned::to_owned)
154				.collect()
155				.await;
156
157			for room_id in all_rooms.iter().chain(rooms_left.iter()) {
158				self.services
159					.account_data
160					.erase_user(user_id, Some(room_id))
161					.await;
162			}
163		}
164
165		for room_id in all_rooms {
166			let state_lock = self.services.state.mutex.lock(&room_id).await;
167
168			// ignore errors
169			if let Err(e) = self
170				.services
171				.membership
172				.leave(user_id, &room_id, None, false, &state_lock)
173				.boxed()
174				.await
175			{
176				warn!(%user_id, "Failed to leave {room_id} remotely: {e}");
177			}
178
179			drop(state_lock);
180
181			self.services
182				.state_cache
183				.forget(&room_id, user_id);
184		}
185
186		Ok(())
187	}
188}