Skip to main content

tuwunel_admin/user/
mod.rs

1mod add_email;
2mod create_user;
3mod deactivate;
4mod deactivate_all;
5mod del_email;
6mod delete_device;
7mod delete_room_tag;
8mod erasure;
9mod force_demote;
10mod force_join_all_local_users;
11mod force_join_list_of_local_users;
12mod force_join_room;
13mod force_leave_room;
14mod force_promote;
15mod get_room_tags;
16mod last_active;
17mod list_joined_rooms;
18mod list_users;
19mod make_user_admin;
20mod put_room_tag;
21mod redact_event;
22mod refresh_profile;
23mod reject_invites;
24mod reset_password;
25mod revoke_admin;
26mod set_profile_key;
27mod unerase;
28
29use clap::{ArgGroup, Subcommand, ValueEnum};
30use futures::FutureExt;
31use ruma::{OwnedDeviceId, OwnedEventId, OwnedRoomId, OwnedRoomOrAliasId, OwnedUserId, UserId};
32use tuwunel_core::Result;
33use tuwunel_service::{Services, profile::Propagation};
34
35use crate::admin_command_dispatch;
36
37const AUTO_GEN_PASSWORD_LENGTH: usize = 25;
38const BULK_JOIN_REASON: &str = "Bulk force joining this room as initiated by the server admin.";
39
40#[derive(Copy, Clone, Debug, Eq, PartialEq, ValueEnum)]
41pub(super) enum PropagateTo {
42	/// Send a member event to every joined room.
43	All,
44
45	/// Send a member event only to rooms whose current per-room value matches
46	/// the user's prior global value.
47	Unchanged,
48
49	/// Send no member events; update the global profile only.
50	None,
51}
52
53impl From<PropagateTo> for Propagation {
54	fn from(propagate_to: PropagateTo) -> Self {
55		match propagate_to {
56			| PropagateTo::All => Self::All,
57			| PropagateTo::Unchanged => Self::Unchanged,
58			| PropagateTo::None => Self::None,
59		}
60	}
61}
62
63#[admin_command_dispatch]
64#[derive(Debug, Subcommand)]
65pub(super) enum UserCommand {
66	/// - Create a new user
67	#[clap(alias = "create")]
68	CreateUser {
69		/// Username of the new user
70		username: String,
71		/// Password of the new user, if unspecified one is generated
72		password: Option<String>,
73	},
74
75	/// - Reset user password
76	ResetPassword {
77		/// Username of the user for whom the password should be reset
78		username: String,
79		/// New password for the user, if unspecified one is generated
80		password: Option<String>,
81	},
82
83	/// - Bind an email address to a local user without verification
84	AddEmail {
85		/// Local user to bind the email address to
86		username: String,
87		/// Email address to bind
88		address: String,
89	},
90
91	/// - Remove an email address binding from a local user
92	DelEmail {
93		/// Local user to remove the email address from
94		username: String,
95		/// Email address to remove
96		address: String,
97	},
98
99	/// - Deactivate a user
100	///
101	/// User will be removed from all rooms by default.
102	/// Use --no-leave-rooms to not leave all rooms by default.
103	Deactivate {
104		#[arg(short, long)]
105		no_leave_rooms: bool,
106		user_id: String,
107	},
108
109	/// - Deactivate a list of users
110	///
111	/// Recommended to use in conjunction with list-local-users.
112	///
113	/// Users will be removed from joined rooms by default.
114	///
115	/// Can be overridden with --no-leave-rooms.
116	///
117	/// Removing a mass amount of users from a room may cause a significant
118	/// amount of leave events. The time to leave rooms may depend significantly
119	/// on joined rooms and servers.
120	///
121	/// This command needs a newline separated list of users provided in a
122	/// Markdown code block below the command.
123	DeactivateAll {
124		#[arg(short, long)]
125		/// Does not leave any rooms the user is in on deactivation
126		no_leave_rooms: bool,
127		#[arg(short, long)]
128		/// Also deactivate admin accounts and will assume leave all rooms too
129		force: bool,
130	},
131
132	/// - Show the MSC4025 erasure state of a local user
133	Erasure {
134		user_id: String,
135	},
136
137	/// - Clear the MSC4025 erasure marker of a local user, restoring the
138	///   unredacted view of their events
139	Unerase {
140		user_id: String,
141	},
142
143	/// - Deletes a user's device.
144	DeleteDevice {
145		user_id: OwnedUserId,
146		device_id: OwnedDeviceId,
147	},
148
149	/// - List local users by recent activity.
150	LastActive {
151		#[arg(short, long)]
152		limit: Option<usize>,
153	},
154
155	/// - List local users in the database
156	#[clap(alias = "list")]
157	ListUsers,
158
159	/// - Lists all the rooms (local and remote) that the specified user is
160	///   joined in
161	ListJoinedRooms {
162		user_id: String,
163	},
164
165	/// - Manually join a local user to a room.
166	ForceJoinRoom {
167		user_id: String,
168		room: OwnedRoomOrAliasId,
169	},
170
171	/// - Manually leave a local user from a room.
172	ForceLeaveRoom {
173		user_id: String,
174		room_id: OwnedRoomOrAliasId,
175	},
176
177	/// - Reject all pending invites for a local user.
178	RejectInvites {
179		user_id: String,
180
181		/// Optional reason attached to each rejection.
182		#[arg(long)]
183		reason: Option<String>,
184	},
185
186	/// - Forces the specified user to drop their power levels to the room
187	///   default, if their permissions allow and the auth check permits
188	ForceDemote {
189		user_id: String,
190		room_id: OwnedRoomOrAliasId,
191	},
192
193	/// - Force promote
194	ForcePromote {
195		user_id: String,
196		room_id: OwnedRoomOrAliasId,
197	},
198
199	/// - Grant server-admin privileges to a user.
200	MakeUserAdmin {
201		user_id: String,
202	},
203
204	/// - Revoke server-admin privileges from a user.
205	RevokeAdmin {
206		user_id: String,
207	},
208
209	/// - Set a user profile key (display name, avatar url, etc) to a value
210	#[command(group(
211		ArgGroup::new("value_or_clear")
212			.required(true)
213			.args(["value", "clear"]),
214	))]
215	SetProfileKey {
216		/// User for whom the profile key should be set; a remote user accepts
217		/// only `--clear`, without `--propagate-to`
218		user_id: String,
219
220		/// Profile key name (e.g. displayname, avatar_url, m.tz, or a custom
221		/// key)
222		key: String,
223
224		/// Value to set (used as string if not parseable as JSON)
225		value: Vec<String>,
226
227		/// Remove the profile key instead of setting a value; a remote user's
228		/// key returns on its next lookup if their server still serves it
229		#[arg(short, long)]
230		clear: bool,
231
232		/// How to propagate the change to the user's joined rooms
233		#[arg(short, long)]
234		propagate_to: Option<PropagateTo>,
235	},
236
237	/// - Re-fetch a remote user's profile, dropping cached fields their server
238	///   no longer serves
239	RefreshProfile {
240		/// Remote user whose cached profile should be refreshed
241		user_id: OwnedUserId,
242	},
243
244	/// - Puts a room tag for the specified user and room ID.
245	///
246	/// This is primarily useful if you'd like to set your admin room
247	/// to the special "System Alerts" section in Element as a way to
248	/// permanently see your admin room without it being buried away in your
249	/// favourites or rooms. To do this, you would pass your user, your admin
250	/// room's internal ID, and the tag name `m.server_notice`.
251	PutRoomTag {
252		user_id: String,
253		room_id: OwnedRoomId,
254		tag: String,
255	},
256
257	/// - Deletes the room tag for the specified user and room ID
258	DeleteRoomTag {
259		user_id: String,
260		room_id: OwnedRoomId,
261		tag: String,
262	},
263
264	/// - Gets all the room tags for the specified user and room ID
265	GetRoomTags {
266		user_id: String,
267		room_id: OwnedRoomId,
268	},
269
270	/// - Attempts to forcefully redact the specified event ID from the sender
271	///   user
272	///
273	/// This is only valid for local users
274	RedactEvent {
275		event_id: OwnedEventId,
276	},
277
278	/// - Force joins a specified list of local users to join the specified
279	///   room.
280	///
281	/// Specify a codeblock of usernames.
282	///
283	/// Requires the `--yes-i-want-to-do-this` flag.
284	ForceJoinListOfLocalUsers {
285		room: OwnedRoomOrAliasId,
286
287		#[arg(long)]
288		yes_i_want_to_do_this: bool,
289	},
290
291	/// - Force joins all local users to the specified room.
292	///
293	/// Requires the `--yes-i-want-to-do-this` flag.
294	ForceJoinAllLocalUsers {
295		room: OwnedRoomOrAliasId,
296
297		#[arg(long)]
298		yes_i_want_to_do_this: bool,
299	},
300}
301
302async fn deactivate_user(services: &Services, user_id: &UserId, no_leave_rooms: bool) -> Result {
303	if !no_leave_rooms {
304		services
305			.deactivate
306			.full_deactivate(user_id, false)
307			.boxed()
308			.await?;
309	} else {
310		services.users.deactivate_account(user_id).await?;
311	}
312
313	Ok(())
314}