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}