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}