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