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