• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

unrenamed / chatd / 9928640664

14 Jul 2024 02:31PM UTC coverage: 16.1% (+3.8%) from 12.265%
9928640664

push

github

unrenamed
fix: `UserConfig` methods visibility

0 of 7 new or added lines in 1 file covered. (0.0%)

472 existing lines in 7 files now uncovered.

379 of 2354 relevant lines covered (16.1%)

0.26 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

0.0
/src/server/session_workflow/command_exec.rs
1
use async_trait::async_trait;
2
use std::fmt::Write;
3

4
use crate::auth::{Auth, BanAttribute, BanQuery};
5
use crate::chat::message::Message;
6
use crate::chat::{
7
    format_commands, message, ChatRoom, Command, CommandProps, OplistCommand, OplistLoadMode,
8
    Theme, TimestampMode, User, UserName, UserStatus, WhitelistCommand, WhitelistLoadMode,
9
    CHAT_COMMANDS, OPLIST_COMMANDS, WHITELIST_COMMANDS,
10
};
11
use crate::terminal::Terminal;
12
use crate::utils::{self, sanitize};
13

14
use super::handler::WorkflowHandler;
15
use super::WorkflowContext;
16

17
#[derive(Default)]
18
pub struct CommandExecutor {
19
    next: Option<Box<dyn WorkflowHandler>>,
20
}
21

22
#[async_trait]
23
impl WorkflowHandler for CommandExecutor {
24
    async fn handle(
25
        &mut self,
26
        context: &mut WorkflowContext,
27
        terminal: &mut Terminal,
28
        room: &mut ChatRoom,
29
        auth: &mut Auth,
30
    ) -> anyhow::Result<()> {
31
        let command = match &context.command {
×
32
            Some(command) => command,
×
33
            None => return Ok(()),
×
34
        };
35
        let user = context.user.clone();
×
36
        let username = &user.username;
×
37

38
        match command {
×
39
            Command::Exit => {
40
                let member = room.find_member(username);
×
41
                member.exit()?;
×
42
            }
43
            Command::Away(reason) => {
×
44
                let member = room.find_member_mut(username);
×
45
                member.user.go_away(reason.to_string());
×
46

47
                let message = message::Emote::new(
48
                    member.user.clone().into(),
×
49
                    format!("has gone away: \"{}\"", reason),
×
50
                );
51
                room.send_message(message.into()).await?;
×
52
            }
53
            Command::Back => {
54
                let member = room.find_member_mut(username);
×
55
                if let UserStatus::Away {
×
56
                    reason: _,
57
                    since: _,
58
                } = &member.user.status
59
                {
60
                    member.user.return_active();
×
61
                    let message =
×
62
                        message::Emote::new(member.user.clone().into(), "is back".to_string());
UNCOV
63
                    room.send_message(message.into()).await?;
×
64
                }
65
            }
66
            Command::Name(new_name) => 'label: {
×
67
                let member = room.find_member_mut(username);
×
68
                let user = member.user.clone();
×
UNCOV
69
                let new_name = sanitize::name(&new_name);
×
70
                let new_username = UserName::from(&new_name);
×
71

72
                if user.username == new_username {
×
73
                    let message = message::Error::new(
UNCOV
74
                        user.into(),
×
75
                        "new name is the same as the original".to_string(),
×
76
                    );
UNCOV
77
                    room.send_message(message.into()).await?;
×
78
                    break 'label;
79
                }
80

81
                if let Some(_) = room.try_find_member(&new_username) {
×
82
                    let message = message::Error::new(
UNCOV
83
                        user.into(),
×
84
                        format!("\"{}\" name is already taken", new_username),
×
85
                    );
UNCOV
86
                    room.send_message(message.into()).await?;
×
87
                    break 'label;
88
                }
89

90
                let message = message::Announce::new(
UNCOV
91
                    user.clone().into(),
×
92
                    format!("user is now known as {}.", new_username),
×
93
                );
94
                room.send_message(message.into()).await?;
×
95

96
                let old_name = user.username;
×
UNCOV
97
                let user_id = user.id;
×
98

99
                let member = room.find_member_mut(username);
×
100
                member.user.set_username(new_username.clone());
×
UNCOV
101
                terminal.set_prompt(&member.user.config.display_name());
×
102

103
                let member = member.clone();
×
104
                room.add_member(new_username.clone(), member);
×
105
                room.remove_member(&old_name);
×
UNCOV
106
                room.add_name(user_id, new_username);
×
107
            }
108
            Command::Msg(to_username, msg) => 'label: {
×
UNCOV
109
                let from = room.find_member(username).user.clone();
×
110
                let to_username = UserName::from(to_username);
×
111

112
                match room.try_find_member_mut(&to_username).map(|a| &mut a.user) {
×
113
                    None => {
114
                        let message =
×
115
                            message::Error::new(from.into(), format!("user is not found"));
UNCOV
116
                        room.send_message(message.into()).await?;
×
117
                        break 'label;
118
                    }
119
                    Some(to) if from.id.eq(&to.id) => {
×
120
                        let message =
×
121
                            message::Error::new(from.into(), format!("you can't message yourself"));
122
                        room.send_message(message.into()).await?;
×
123
                        break 'label;
124
                    }
125
                    Some(to) => {
×
126
                        let status = to.status.clone();
×
127
                        let name = to.username.clone();
×
128

129
                        to.set_reply_to(from.id);
×
130

131
                        let message = message::Private::new(
UNCOV
132
                            from.clone().into(),
×
133
                            to.clone().into(),
×
UNCOV
134
                            msg.to_string(),
×
135
                        );
136
                        room.send_message(message.into()).await?;
×
137

138
                        match status {
×
139
                            UserStatus::Away { reason, since: _ } => {
×
140
                                let message = message::System::new(
UNCOV
141
                                    from.into(),
×
UNCOV
142
                                    format!(
×
143
                                        "Sent PM to {}, but they're away now: {}",
144
                                        name, reason
145
                                    ),
146
                                );
UNCOV
147
                                room.send_message(message.into()).await?;
×
148
                            }
149
                            UserStatus::Active => {}
150
                        }
151
                    }
152
                }
153
            }
UNCOV
154
            Command::Reply(message_body) => 'label: {
×
155
                let member = room.find_member(username);
×
156
                let from = member.user.clone();
×
157

158
                if from.reply_to.is_none() {
×
UNCOV
159
                    let message =
×
160
                        message::Error::new(from.into(), "no message to reply to".to_string());
UNCOV
161
                    room.send_message(message.into()).await?;
×
162
                    break 'label;
163
                }
164

165
                let target_id = &from.reply_to.unwrap();
×
UNCOV
166
                let target_name = room.try_get_name(&target_id);
×
167
                if target_name.is_none() {
×
UNCOV
168
                    let message =
×
169
                        message::Error::new(from.into(), "user already left the room".to_string());
UNCOV
170
                    room.send_message(message.into()).await?;
×
171
                    break 'label;
172
                }
173

174
                let member = room.find_member(target_name.unwrap());
×
UNCOV
175
                let to = member.user.clone();
×
UNCOV
176
                let message =
×
177
                    message::Private::new(from.into(), to.into(), (*message_body).to_string());
178
                room.send_message(message.into()).await?;
×
179
            }
180
            Command::Users => {
181
                let member = room.find_member(username);
×
UNCOV
182
                let user = member.user.clone();
×
183

UNCOV
184
                let mut usernames = room.names().values().collect::<Vec<&UserName>>();
×
185
                usernames.sort_by_key(|a| a.to_lowercase());
×
186

UNCOV
187
                let colorized_names = usernames
×
188
                    .iter()
UNCOV
189
                    .map(|u| user.config.theme().style_username(u).to_string())
×
190
                    .collect::<Vec<String>>();
191

UNCOV
192
                let body = format!(
×
193
                    "{} connected: {}",
194
                    room.names().len(),
×
195
                    colorized_names.join(", ")
×
196
                );
197

198
                let message = message::System::new(user.into(), body);
×
199
                room.send_message(message.into()).await?;
×
200
            }
201
            Command::Whois(target_username) => {
×
202
                let member = room.find_member(username);
×
UNCOV
203
                let user = member.user.clone();
×
204
                let target_username = UserName::from(target_username);
×
205
                let message = match room
×
UNCOV
206
                    .try_find_member(&target_username)
×
207
                    .map(|member| &member.user)
×
208
                {
209
                    Some(target) => message::System::new(user.into(), target.to_string()).into(),
×
210
                    None => message::Error::new(user.into(), "user not found".to_string()).into(),
×
211
                };
UNCOV
212
                room.send_message(message).await?;
×
213
            }
UNCOV
214
            Command::Slap(target_username) => 'label: {
×
215
                let member = room.find_member(username);
×
216
                let user = member.user.clone();
×
217

218
                if target_username.is_none() {
×
219
                    let message = message::Emote::new(
UNCOV
220
                        user.into(),
×
UNCOV
221
                        "hits himself with a squishy banana.".to_string(),
×
222
                    );
223
                    room.send_message(message.into()).await?;
×
224
                    break 'label;
225
                }
226

227
                let target_username = UserName::from(target_username.as_deref().unwrap());
×
228
                let target = room
×
UNCOV
229
                    .try_find_member_mut(&target_username)
×
UNCOV
230
                    .map(|member| &member.user);
×
231

UNCOV
232
                let message = if let Some(t) = target {
×
233
                    message::Emote::new(
234
                        user.into(),
×
UNCOV
235
                        format!("hits {} with a squishy banana.", t.username),
×
236
                    )
237
                    .into()
238
                } else {
239
                    message::Error::new(
240
                        user.into(),
×
UNCOV
241
                        "that slippin' monkey not in the room".to_string(),
×
242
                    )
243
                    .into()
244
                };
UNCOV
245
                room.send_message(message).await?;
×
246
            }
247
            Command::Shrug => {
248
                let member = room.find_member(username);
×
249
                let user = member.user.clone();
×
UNCOV
250
                let message = message::Emote::new(user.into(), "¯\\_(◕‿◕)_/¯".to_string());
×
UNCOV
251
                room.send_message(message.into()).await?;
×
252
            }
UNCOV
253
            Command::Me(action) => {
×
UNCOV
254
                let member = room.find_member(username);
×
255
                let user = member.user.clone();
×
256
                let message = message::Emote::new(
UNCOV
257
                    user.into(),
×
258
                    match action {
×
UNCOV
259
                        Some(s) => format!("{}", s),
×
260
                        None => format!("is at a loss for words."),
×
261
                    },
262
                );
263
                room.send_message(message.into()).await?;
×
264
            }
265
            Command::Help => {
266
                let member = room.find_member(username);
×
UNCOV
267
                let user = member.user.clone();
×
268

269
                let mut to_display: Vec<&Command> = CHAT_COMMANDS
×
270
                    .iter()
271
                    .filter(|cmd| cmd.is_visible())
×
272
                    .collect();
273

274
                to_display.sort_by(|a, b| a.cmd().len().cmp(&b.cmd().len()));
×
275

276
                let mut help = format!("Available commands: {}", utils::NEWLINE);
×
277
                let noop_commands: Vec<&Command> = to_display
×
278
                    .iter()
279
                    .filter(|c| !c.is_op())
×
UNCOV
280
                    .map(|c| *c)
×
281
                    .collect();
UNCOV
282
                help.push_str(&format_commands(noop_commands));
×
283

284
                if auth.is_op(&user.public_key.clone().into()) {
×
UNCOV
285
                    let op_commands: Vec<&Command> = to_display
×
286
                        .iter()
UNCOV
287
                        .filter(|c| c.is_op())
×
288
                        .map(|c| *c)
×
289
                        .collect();
UNCOV
290
                    help.push_str(&format!(
×
291
                        "{}{}Operator commands: {}{}",
292
                        utils::NEWLINE,
293
                        utils::NEWLINE,
294
                        utils::NEWLINE,
295
                        &format_commands(op_commands)
×
296
                    ));
297
                }
298

UNCOV
299
                let message = message::System::new(user.into(), help);
×
UNCOV
300
                room.send_message(message.into()).await?;
×
301
            }
302
            Command::Quiet => {
UNCOV
303
                let member = room.find_member_mut(username);
×
304
                member.user.config.switch_quiet_mode();
×
305
                let message = message::System::new(
306
                    member.user.clone().into(),
×
UNCOV
307
                    match member.user.config.quiet() {
×
308
                        true => "Quiet mode is toggled ON",
×
309
                        false => "Quiet mode is toggled OFF",
×
310
                    }
311
                    .to_string(),
312
                );
313
                room.send_message(message.into()).await?;
×
314
            }
UNCOV
315
            Command::Timestamp(mode) => {
×
UNCOV
316
                let member = room.find_member_mut(username);
×
317
                member.user.config.set_timestamp_mode(*mode);
×
318
                let message = message::System::new(
319
                    member.user.clone().into(),
×
320
                    match member.user.config.timestamp_mode() {
×
321
                        TimestampMode::Time | TimestampMode::DateTime => {
322
                            "Timestamp is toggled ON, timezone is UTC"
×
323
                        }
324
                        TimestampMode::Off => "Timestamp is toggled OFF",
×
325
                    }
326
                    .to_string(),
327
                );
328
                room.send_message(message.into()).await?;
×
329
            }
330
            Command::Theme(theme) => {
×
331
                let member = room.find_member_mut(username);
×
UNCOV
332
                let message = message::System::new(user.into(), format!("Set theme: {}", theme));
×
333
                member.user.set_theme((*theme).into());
×
UNCOV
334
                terminal.set_prompt(&member.user.config.display_name());
×
335
                room.send_message(message.into()).await?;
×
336
            }
337
            Command::Themes => {
UNCOV
338
                let member = room.find_member(username);
×
339
                let user = member.user.clone();
×
340
                let message = message::System::new(
UNCOV
341
                    user.into(),
×
UNCOV
342
                    format!("Supported themes: {}", Theme::values().join(", ")),
×
343
                );
344
                room.send_message(message.into()).await?;
×
345
            }
UNCOV
346
            Command::Ignore(target) => 'label: {
×
347
                let member = room.find_member(username);
×
348
                let user = member.user.clone();
×
349

UNCOV
350
                if target.is_none() {
×
351
                    let ignored_usernames: Vec<String> = user
×
352
                        .ignored
353
                        .iter()
UNCOV
354
                        .filter_map(|id| room.try_get_name(id))
×
UNCOV
355
                        .map(|name| user.config.theme().style_username(name).to_string())
×
356
                        .collect();
357

UNCOV
358
                    let message_text = match ignored_usernames.is_empty() {
×
UNCOV
359
                        true => "0 users ignored".to_string(),
×
UNCOV
360
                        false => format!(
×
361
                            "{} users ignored: {}",
362
                            ignored_usernames.len(),
×
363
                            ignored_usernames.join(", ")
×
364
                        ),
365
                    };
366

UNCOV
367
                    let message = message::System::new(user.into(), message_text);
×
368
                    room.send_message(message.into()).await?;
×
369
                    break 'label;
370
                }
371

UNCOV
372
                let target_username = UserName::from(target.as_deref().unwrap());
×
UNCOV
373
                match room
×
374
                    .try_find_member(&target_username)
×
UNCOV
375
                    .map(|a| a.user.id.clone())
×
376
                {
377
                    Some(target_id) if target_id == user.id => {
×
378
                        let message = message::Error::new(
379
                            user.into(),
×
UNCOV
380
                            "you can't ignore yourself".to_string(),
×
381
                        );
UNCOV
382
                        room.send_message(message.into()).await?;
×
383
                        break 'label;
384
                    }
385
                    Some(target_id) if user.ignored.contains(&target_id) => {
×
386
                        let message = message::System::new(
UNCOV
387
                            user.into(),
×
388
                            format!("user already in the ignored list"),
×
389
                        );
UNCOV
390
                        room.send_message(message.into()).await?;
×
391
                        break 'label;
392
                    }
393
                    None => {
UNCOV
394
                        let message =
×
395
                            message::Error::new(user.into(), "user not found".to_string());
UNCOV
396
                        room.send_message(message.into()).await?;
×
397
                        break 'label;
398
                    }
399
                    Some(target_id) => {
×
400
                        room.find_member_mut(username)
×
401
                            .user
402
                            .ignored
403
                            .insert(target_id);
404
                        let message = message::System::new(
405
                            user.into(),
×
UNCOV
406
                            format!("Ignoring: {}", target_username),
×
407
                        );
408
                        room.send_message(message.into()).await?;
×
409
                    }
410
                }
411
            }
UNCOV
412
            Command::Unignore(target_username) => 'label: {
×
413
                let member = room.find_member(username);
×
UNCOV
414
                let user = member.user.clone();
×
415

416
                let target_username = UserName::from(target_username);
×
UNCOV
417
                match room
×
418
                    .try_find_member(&target_username)
×
UNCOV
419
                    .map(|a| a.user.id.clone())
×
420
                {
421
                    None => {
422
                        let message =
×
423
                            message::Error::new(user.into(), "user not found".to_string());
UNCOV
424
                        room.send_message(message.into()).await?;
×
425
                        break 'label;
426
                    }
427
                    Some(target_id) if !user.ignored.contains(&target_id) => {
×
428
                        let message = message::Error::new(
UNCOV
429
                            user.into(),
×
430
                            "user not in the ignored list yet".to_string(),
×
431
                        );
UNCOV
432
                        room.send_message(message.into()).await?;
×
433
                        break 'label;
434
                    }
435
                    Some(target_id) => {
×
436
                        room.find_member_mut(username)
×
437
                            .user
438
                            .ignored
439
                            .remove(&target_id);
×
440
                        let message = message::System::new(
UNCOV
441
                            user.into(),
×
442
                            format!("No longer ignoring: {}", target_username),
×
443
                        );
UNCOV
444
                        room.send_message(message.into()).await?;
×
445
                    }
446
                }
447
            }
448
            Command::Focus(target) => 'label: {
×
UNCOV
449
                let member = room.find_member(username);
×
450
                let user = member.user.clone();
×
451

UNCOV
452
                if target.is_none() {
×
UNCOV
453
                    let focused_usernames: Vec<String> = user
×
454
                        .focused
455
                        .iter()
456
                        .filter_map(|id| room.try_get_name(id))
×
UNCOV
457
                        .map(|name| user.config.theme().style_username(name).to_string())
×
458
                        .collect();
459

460
                    let message_text = match focused_usernames.is_empty() {
×
461
                        true => "Focusing no users".to_string(),
×
462
                        false => format!(
×
463
                            "Focusing on {} users: {}",
UNCOV
464
                            focused_usernames.len(),
×
465
                            focused_usernames.join(", ")
×
466
                        ),
467
                    };
468

469
                    let message = message::System::new(user.into(), message_text);
×
470
                    room.send_message(message.into()).await?;
×
471
                    break 'label;
472
                }
473

UNCOV
474
                let target = target.as_deref().unwrap();
×
UNCOV
475
                if target == "$" {
×
476
                    room.find_member_mut(username).user.focused.clear();
×
477
                    let message = message::System::new(
478
                        user.into(),
×
479
                        "Removed focus from all users".to_string(),
×
480
                    );
UNCOV
481
                    room.send_message(message.into()).await?;
×
482
                    break 'label;
483
                }
484

UNCOV
485
                let mut focused = vec![];
×
UNCOV
486
                for target_username in target.split(",") {
×
UNCOV
487
                    let target_username = UserName::from(target_username);
×
UNCOV
488
                    match room
×
489
                        .try_find_member(&target_username)
UNCOV
490
                        .map(|a| a.user.id.clone())
×
491
                    {
492
                        None => continue,
UNCOV
493
                        Some(target_id) if target_id == user.id => continue,
×
494
                        Some(target_id) if user.focused.contains(&target_id) => continue,
×
495
                        Some(target_id) => {
×
496
                            room.find_member_mut(username)
×
497
                                .user
498
                                .focused
499
                                .insert(target_id);
500

UNCOV
501
                            focused.push(target_username);
×
502
                        }
503
                    }
504
                }
505

UNCOV
506
                let focused_usernames: Vec<String> = focused
×
507
                    .iter()
508
                    .map(|name| user.config.theme().style_username(name).to_string())
×
509
                    .collect();
510

511
                let message_text = match focused_usernames.is_empty() {
×
512
                    true => "No online users found to focus".to_string(),
×
UNCOV
513
                    false => format!(
×
514
                        "Focusing on {} users: {}",
515
                        focused_usernames.len(),
×
516
                        focused_usernames.join(", ")
×
517
                    ),
518
                };
519

UNCOV
520
                let message = message::System::new(user.into(), message_text);
×
521
                room.send_message(message.into()).await?;
×
522
            }
523
            Command::Version => {
UNCOV
524
                let message =
×
525
                    message::System::new(user.into(), format!("{}", env!("CARGO_PKG_VERSION")));
526
                room.send_message(message.into()).await?;
×
527
            }
528
            Command::Uptime => {
UNCOV
529
                let message = message::System::new(user.into(), room.uptime());
×
530
                room.send_message(message.into()).await?;
×
531
            }
UNCOV
532
            Command::Mute(target_username) => 'label: {
×
533
                if !auth.is_op(&user.public_key.clone().into()) {
×
UNCOV
534
                    let message =
×
535
                        message::Error::new(user.into(), "must be an operator".to_string());
536
                    room.send_message(message.into()).await?;
×
537
                    break 'label;
538
                }
539

540
                let target_username = UserName::from(target_username);
×
541
                match room
×
UNCOV
542
                    .try_find_member_mut(&target_username)
×
543
                    .map(|a| &mut a.user)
×
544
                {
545
                    None => {
UNCOV
546
                        let message =
×
547
                            message::Error::new(user.into(), "user not found".to_string());
UNCOV
548
                        room.send_message(message.into()).await?;
×
549
                        break 'label;
550
                    }
551
                    Some(target) if target.id == user.id => {
×
UNCOV
552
                        let message =
×
553
                            message::Error::new(user.into(), "you can't mute yourself".to_string());
UNCOV
554
                        room.send_message(message.into()).await?;
×
555
                        break 'label;
556
                    }
557
                    Some(target) => {
×
558
                        target.switch_mute_mode();
×
UNCOV
559
                        let target = target.clone();
×
560
                        let message = message::System::new(
UNCOV
561
                            user.into(),
×
562
                            format!(
×
563
                                "{}: {}, id = {}",
564
                                match target.is_muted {
×
565
                                    true => "Muted",
×
UNCOV
566
                                    false => "Unmuted",
×
567
                                },
568
                                target.username,
569
                                target.id
570
                            ),
571
                        );
UNCOV
572
                        room.send_message(message.into()).await?;
×
573
                    }
574
                }
575
            }
UNCOV
576
            Command::Motd(new_motd) => 'label: {
×
UNCOV
577
                if new_motd.is_none() {
×
578
                    let message = message::System::new(user.into(), room.motd().clone());
×
UNCOV
579
                    room.send_message(message.into()).await?;
×
580
                    break 'label;
581
                }
582

583
                if !auth.is_op(&user.public_key.clone().into()) {
×
584
                    let message = message::Error::new(
585
                        user.into(),
×
586
                        "must be an operator to modify the MOTD".to_string(),
×
587
                    );
UNCOV
588
                    room.send_message(message.into()).await?;
×
589
                    break 'label;
590
                }
591

592
                room.set_motd(new_motd.as_deref().unwrap().to_string());
×
593

594
                let message = message::Announce::new(
UNCOV
595
                    user.into(),
×
596
                    format!(
×
597
                        "set new message of the day: {}-> {}",
598
                        utils::NEWLINE,
599
                        room.motd()
×
600
                    ),
601
                );
602
                room.send_message(message.into()).await?;
×
603
            }
UNCOV
604
            Command::Kick(target_username) => 'label: {
×
UNCOV
605
                if !auth.is_op(&user.public_key.clone().into()) {
×
606
                    let message =
×
607
                        message::Error::new(user.into(), "must be an operator".to_string());
608
                    room.send_message(message.into()).await?;
×
609
                    break 'label;
610
                }
611

UNCOV
612
                let target_username = UserName::from(target_username);
×
613
                match room.try_find_member_mut(&target_username) {
×
614
                    None => {
615
                        let message =
×
616
                            message::Error::new(user.into(), "user not found".to_string());
UNCOV
617
                        room.send_message(message.into()).await?;
×
618
                        break 'label;
619
                    }
620
                    Some(member) => {
×
621
                        let message = message::Announce::new(
622
                            user.into(),
×
623
                            format!("kicked {} from the server", target_username),
×
624
                        );
625
                        member.exit()?;
×
626
                        room.send_message(message.into()).await?;
×
627
                    }
628
                }
629
            }
630
            Command::Ban(query) => 'label: {
×
UNCOV
631
                if !auth.is_op(&user.public_key.clone().into()) {
×
UNCOV
632
                    let message =
×
633
                        message::Error::new(user.into(), "must be an operator".to_string());
634
                    room.send_message(message.into()).await?;
×
635
                    break 'label;
636
                }
637

638
                let query = query.parse::<BanQuery>();
×
UNCOV
639
                if let Err(err) = query {
×
UNCOV
640
                    let message = message::Error::new(user.into(), err.to_string());
×
641
                    room.send_message(message.into()).await?;
×
642
                    break 'label;
643
                }
644

UNCOV
645
                let mut messages: Vec<Message> = vec![];
×
646

UNCOV
647
                match query.unwrap() {
×
648
                    BanQuery::Single { name, duration } => {
×
649
                        let target_username = UserName::from(&name);
×
650
                        match room.try_find_member(&target_username) {
×
651
                            Some(member) => {
×
652
                                auth.ban_fingerprint(
×
UNCOV
653
                                    &member.user.public_key.fingerprint(),
×
654
                                    duration,
655
                                );
656
                                let message = message::Announce::new(
657
                                    user.clone().into(),
×
658
                                    format!("banned {} from the server", member.user.username),
×
659
                                );
660
                                member.exit()?;
×
661
                                messages.push(message.into());
×
662
                            }
663
                            None => {
UNCOV
664
                                let message =
×
665
                                    message::Error::new(user.into(), "user not found".to_string());
666
                                room.send_message(message.into()).await?;
×
667
                                break 'label;
668
                            }
669
                        }
670
                    }
UNCOV
671
                    BanQuery::Multiple(items) => {
×
672
                        for item in items {
×
673
                            match item.attribute {
×
UNCOV
674
                                BanAttribute::Name(name) => {
×
UNCOV
675
                                    let username = UserName::from(&name);
×
UNCOV
676
                                    auth.ban_username(&username, item.duration);
×
677

678
                                    for (_, member) in room.members_iter_mut() {
×
679
                                        if member.user.username.eq(&username) {
×
680
                                            let message = message::Announce::new(
UNCOV
681
                                                user.clone().into(),
×
UNCOV
682
                                                format!("banned {} from the server", username),
×
683
                                            );
UNCOV
684
                                            messages.push(message.into());
×
UNCOV
685
                                            member.exit()?;
×
686
                                        }
687
                                    }
688
                                }
UNCOV
689
                                BanAttribute::Fingerprint(fingerprint) => {
×
UNCOV
690
                                    auth.ban_fingerprint(&fingerprint, item.duration);
×
691

692
                                    for (_, member) in room.members_iter_mut() {
×
UNCOV
693
                                        if member.user.public_key.fingerprint().eq(&fingerprint) {
×
694
                                            let message = message::Announce::new(
UNCOV
695
                                                user.clone().into(),
×
696
                                                format!(
×
697
                                                    "banned {} from the server",
698
                                                    member.user.username
699
                                                ),
700
                                            );
701
                                            messages.push(message.into());
×
702
                                            member.exit()?;
×
703
                                        }
704
                                    }
705
                                }
706
                                BanAttribute::Ip(_) => todo!(),
707
                            }
708
                        }
709
                    }
710
                }
711

712
                let message = message::System::new(
UNCOV
713
                    user.into(),
×
UNCOV
714
                    "Banning is complete. Offline users were silently banned.".to_string(),
×
715
                );
716
                messages.push(message.into());
×
717

UNCOV
718
                for message in messages {
×
UNCOV
719
                    room.send_message(message).await?;
×
720
                }
721
            }
722
            Command::Banned => 'label: {
×
UNCOV
723
                if !auth.is_op(&user.public_key.clone().into()) {
×
724
                    let message =
×
725
                        message::Error::new(user.into(), "must be an operator".to_string());
726
                    room.send_message(message.into()).await?;
×
727
                    break 'label;
728
                }
729

UNCOV
730
                let (names, fingerprints) = auth.banned();
×
731
                let mut banned = String::new();
×
UNCOV
732
                write!(banned, "Banned:").expect("Failed to write banned members to string");
×
733

734
                for name in names {
×
735
                    write!(banned, "{} \"name={}\"", utils::NEWLINE, name)
×
736
                        .expect("Failed to write banned members to string");
737
                }
738

UNCOV
739
                for fingerprint in fingerprints {
×
740
                    write!(banned, "{} \"fingerprint={}\"", utils::NEWLINE, fingerprint)
×
741
                        .expect("Failed to write banned members to string");
742
                }
743

744
                let message = message::System::new(user.into(), banned);
×
UNCOV
745
                room.send_message(message.into()).await?;
×
746
            }
747
            Command::Whitelist(command) => 'label: {
×
UNCOV
748
                if !auth.is_op(&user.public_key.clone().into()) {
×
UNCOV
749
                    let message =
×
750
                        message::Error::new(user.into(), "must be an operator".to_string());
UNCOV
751
                    room.send_message(message.into()).await?;
×
752
                    break 'label;
753
                }
754

UNCOV
755
                exec_whitelist_command(command, &user, room, auth).await?;
×
756
            }
UNCOV
757
            Command::Oplist(command) => 'label: {
×
758
                if !auth.is_op(&user.public_key.clone().into()) {
×
UNCOV
759
                    let message =
×
760
                        message::Error::new(user.into(), "must be an operator".to_string());
UNCOV
761
                    room.send_message(message.into()).await?;
×
762
                    break 'label;
763
                }
764

765
                exec_oplist_command(command, &user, room, auth).await?;
×
766
            }
767
        }
768

UNCOV
769
        Ok(())
×
770
    }
771

UNCOV
772
    fn next(&mut self) -> &mut Option<Box<dyn WorkflowHandler>> {
×
773
        &mut self.next
774
    }
775
}
776

777
async fn exec_whitelist_command(
×
778
    command: &WhitelistCommand,
779
    user: &User,
780
    room: &mut ChatRoom,
781
    auth: &mut Auth,
782
) -> anyhow::Result<()> {
783
    match command {
×
784
        WhitelistCommand::On => {
UNCOV
785
            auth.enable_whitelist_mode();
×
786
            let message = message::System::new(
787
                user.into(),
×
788
                "Server whitelisting is now enabled".to_string(),
×
789
            );
790
            room.send_message(message.into()).await?;
×
791
        }
792
        WhitelistCommand::Off => {
793
            auth.disable_whitelist_mode();
×
794
            let message = message::System::new(
UNCOV
795
                user.into(),
×
796
                "Server whitelisting is now disabled".to_string(),
×
797
            );
798
            room.send_message(message.into()).await?;
×
799
        }
UNCOV
800
        WhitelistCommand::Add(users_or_keys) => {
×
801
            let mut invalid_keys = vec![];
×
UNCOV
802
            let mut invalid_users = vec![];
×
803

UNCOV
804
            let mut is_key = false;
×
UNCOV
805
            for user_or_key in users_or_keys.split_whitespace() {
×
806
                if user_or_key.starts_with("ssh-") {
×
807
                    is_key = true;
×
808
                    continue;
809
                }
810

811
                if is_key {
×
UNCOV
812
                    let key = user_or_key;
×
813
                    match russh_keys::parse_public_key_base64(&key) {
×
814
                        Ok(pk) => auth.add_trusted_key(pk.into()),
×
UNCOV
815
                        Err(_) => invalid_keys.push(key.to_string()),
×
816
                    }
UNCOV
817
                    is_key = false;
×
818
                } else {
819
                    let user = user_or_key;
820
                    let username = UserName::from(user);
×
821
                    match room.try_find_member(&username).map(|m| &m.user) {
×
UNCOV
822
                        Some(user) => auth.add_trusted_key(user.public_key.clone().into()),
×
UNCOV
823
                        None => invalid_users.push(user.to_string()),
×
824
                    }
825
                }
826
            }
827

828
            let mut messages = vec![];
×
829
            if !invalid_keys.is_empty() {
×
830
                messages.push(format!("Invalid keys: {}", invalid_keys.join(", ")));
×
831
            }
832
            if !invalid_users.is_empty() {
×
833
                messages.push(format!("Invalid users: {}", invalid_users.join(", ")));
×
834
            }
835
            if messages.is_empty() {
×
UNCOV
836
                messages.push(format!("Server whitelist is updated!"));
×
837
            }
838

839
            let message = message::System::new(user.into(), messages.join(utils::NEWLINE));
×
840
            room.send_message(message.into()).await?;
×
841
        }
842
        WhitelistCommand::Remove(users_or_keys) => {
×
843
            let mut invalid_keys = vec![];
×
UNCOV
844
            let mut invalid_users = vec![];
×
845

UNCOV
846
            let mut is_key = false;
×
UNCOV
847
            for user_or_key in users_or_keys.split_whitespace() {
×
848
                if user_or_key.starts_with("ssh-") {
×
849
                    is_key = true;
×
850
                    continue;
851
                }
852

853
                if is_key {
×
UNCOV
854
                    let key = user_or_key;
×
UNCOV
855
                    match russh_keys::parse_public_key_base64(&key) {
×
UNCOV
856
                        Ok(pk) => auth.remove_trusted_key(pk.into()),
×
UNCOV
857
                        Err(_) => invalid_keys.push(key.to_string()),
×
858
                    }
859
                    is_key = false;
×
860
                } else {
861
                    let user = user_or_key;
862
                    let username = UserName::from(user);
×
863
                    match room.try_find_member(&username).map(|m| &m.user) {
×
UNCOV
864
                        Some(user) => auth.remove_trusted_key(user.public_key.clone()),
×
865
                        None => invalid_users.push(user.to_string()),
×
866
                    }
867
                }
868
            }
869

UNCOV
870
            let mut messages = vec![];
×
UNCOV
871
            if !invalid_keys.is_empty() {
×
872
                messages.push(format!("Invalid keys: {}", invalid_keys.join(", ")));
×
873
            }
UNCOV
874
            if !invalid_users.is_empty() {
×
UNCOV
875
                messages.push(format!("Invalid users: {}", invalid_users.join(", ")));
×
876
            }
877
            if messages.is_empty() {
×
UNCOV
878
                messages.push(format!("Server whitelist is updated!"));
×
879
            }
880

881
            let message = message::System::new(user.into(), messages.join(utils::NEWLINE));
×
UNCOV
882
            room.send_message(message.into()).await?;
×
883
        }
UNCOV
884
        WhitelistCommand::Load(mode) => {
×
885
            if *mode == WhitelistLoadMode::Replace {
×
886
                auth.clear_trusted_keys();
×
887
            }
888
            let message: Message = match auth.load_trusted_keys() {
×
889
                Ok(_) => {
890
                    let body = "Trusted keys are up-to-date with the whitelist file".to_string();
×
UNCOV
891
                    message::System::new(user.into(), body).into()
×
892
                }
893
                Err(err) => {
×
UNCOV
894
                    let body = err.to_string();
×
UNCOV
895
                    message::Error::new(user.into(), body).into()
×
896
                }
897
            };
898
            room.send_message(message).await?;
×
899
        }
900
        WhitelistCommand::Save => {
901
            let message: Message = match auth.save_trusted_keys() {
×
902
                Ok(_) => {
903
                    let body = "Whitelist file is up-to-date with the trusted keys".to_string();
×
UNCOV
904
                    message::System::new(user.into(), body).into()
×
905
                }
906
                Err(err) => {
×
UNCOV
907
                    let body = err.to_string();
×
908
                    message::Error::new(user.into(), body).into()
×
909
                }
910
            };
911
            room.send_message(message).await?;
×
912
        }
UNCOV
913
        WhitelistCommand::Reverify => 'label: {
×
914
            if !auth.is_whitelist_enabled() {
×
915
                let message = message::System::new(
UNCOV
916
                    user.into(),
×
UNCOV
917
                    "Whitelist is disabled, so nobody will be kicked".to_string(),
×
918
                );
919
                room.send_message(message.into()).await?;
×
920
                break 'label;
921
            }
922

923
            let auth = auth;
UNCOV
924
            let mut kicked = vec![];
×
UNCOV
925
            for (_, member) in room.members_iter() {
×
926
                if !auth.is_trusted(&member.user.public_key) {
×
UNCOV
927
                    kicked.push(member.user.clone());
×
928
                    member.exit()?;
×
929
                }
930
            }
931

UNCOV
932
            for user in kicked {
×
933
                let message = message::Announce::new(
UNCOV
934
                    user.into(),
×
935
                    "was kicked during pubkey reverification".to_string(),
×
936
                );
UNCOV
937
                room.send_message(message.into()).await?;
×
938
            }
939
        }
940
        WhitelistCommand::Status => {
941
            let auth = auth;
UNCOV
942
            let mut messages: Vec<String> = vec![];
×
943

UNCOV
944
            messages.push(
×
945
                String::from("Server whitelisting is ")
×
946
                    + match auth.is_whitelist_enabled() {
×
947
                        true => "enabled",
×
948
                        false => "disabled",
×
949
                    },
950
            );
951

UNCOV
952
            let mut trusted_online_users: Vec<String> = vec![];
×
953
            let mut trusted_keys = vec![];
×
954

UNCOV
955
            for key in auth.trusted_keys() {
×
956
                if let Some(user) = room
×
957
                    .members_iter()
UNCOV
958
                    .map(|(_, m)| &m.user)
×
959
                    .find(|u| u.public_key == *key)
×
960
                {
UNCOV
961
                    trusted_online_users.push(user.username.clone().into());
×
962
                } else {
UNCOV
963
                    trusted_keys.push(key.fingerprint());
×
964
                }
965
            }
966

UNCOV
967
            if !trusted_online_users.is_empty() {
×
968
                messages.push(format!(
×
969
                    "Trusted online users: {}",
UNCOV
970
                    trusted_online_users.join(", ")
×
971
                ));
972
            }
973

UNCOV
974
            if !trusted_keys.is_empty() {
×
975
                messages.push(format!("Trusted offline keys: {}", trusted_keys.join(", ")));
×
976
            }
977

UNCOV
978
            let message = message::System::new(user.into(), messages.join(utils::NEWLINE));
×
979
            room.send_message(message.into()).await?;
×
980
        }
981
        WhitelistCommand::Help => {
UNCOV
982
            let mut to_display: Vec<&WhitelistCommand> = WHITELIST_COMMANDS
×
983
                .iter()
UNCOV
984
                .filter(|cmd| cmd.is_visible())
×
985
                .collect();
986

UNCOV
987
            to_display.sort_by(|a, b| a.cmd().len().cmp(&b.cmd().len()));
×
988

UNCOV
989
            let mut help = format!("Available commands: {}", utils::NEWLINE);
×
990
            help.push_str(&format_commands(to_display));
×
991

UNCOV
992
            let message = message::System::new(user.into(), help);
×
993
            room.send_message(message.into()).await?;
×
994
        }
995
    }
996

UNCOV
997
    Ok(())
×
998
}
999

UNCOV
1000
async fn exec_oplist_command(
×
1001
    command: &OplistCommand,
1002
    user: &User,
1003
    room: &mut ChatRoom,
1004
    auth: &mut Auth,
1005
) -> anyhow::Result<()> {
UNCOV
1006
    match command {
×
1007
        OplistCommand::Add(users_or_keys) => {
×
1008
            let mut invalid_keys = vec![];
×
1009
            let mut invalid_users = vec![];
×
1010

1011
            let mut is_key = false;
×
UNCOV
1012
            for user_or_key in users_or_keys.split_whitespace() {
×
1013
                if user_or_key.starts_with("ssh-") {
×
1014
                    is_key = true;
×
1015
                    continue;
1016
                }
1017

UNCOV
1018
                if is_key {
×
UNCOV
1019
                    let key = user_or_key;
×
1020
                    match russh_keys::parse_public_key_base64(&key) {
×
1021
                        Ok(pk) => auth.add_operator(pk.into()),
×
1022
                        Err(_) => invalid_keys.push(key.to_string()),
×
1023
                    }
1024
                    is_key = false;
×
1025
                } else {
1026
                    let user = user_or_key;
UNCOV
1027
                    let username = UserName::from(user);
×
UNCOV
1028
                    match room.try_find_member(&username).map(|m| &m.user) {
×
1029
                        Some(user) => auth.add_operator(user.public_key.clone()),
×
1030
                        None => invalid_users.push(user.to_string()),
×
1031
                    }
1032
                }
1033
            }
1034

UNCOV
1035
            let mut messages = vec![];
×
1036
            if !invalid_keys.is_empty() {
×
1037
                messages.push(format!("Invalid keys: {}", invalid_keys.join(", ")));
×
1038
            }
UNCOV
1039
            if !invalid_users.is_empty() {
×
UNCOV
1040
                messages.push(format!("Invalid users: {}", invalid_users.join(", ")));
×
1041
            }
1042

UNCOV
1043
            if messages.is_empty() {
×
UNCOV
1044
                messages.push(format!("Server operators list is updated!"));
×
1045
            }
1046

1047
            let message = message::System::new(user.into(), messages.join(utils::NEWLINE));
×
1048
            room.send_message(message.into()).await?;
×
1049
        }
1050
        OplistCommand::Remove(users_or_keys) => {
×
1051
            let mut invalid_keys = vec![];
×
UNCOV
1052
            let mut invalid_users = vec![];
×
1053

1054
            let mut is_key = false;
×
UNCOV
1055
            for user_or_key in users_or_keys.split_whitespace() {
×
1056
                if user_or_key.starts_with("ssh-") {
×
UNCOV
1057
                    is_key = true;
×
1058
                    continue;
1059
                }
1060

1061
                if is_key {
×
UNCOV
1062
                    let key = user_or_key;
×
UNCOV
1063
                    match russh_keys::parse_public_key_base64(&key) {
×
1064
                        Ok(pk) => auth.remove_operator(pk.into()),
×
1065
                        Err(_) => invalid_keys.push(key.to_string()),
×
1066
                    }
1067
                    is_key = false;
×
1068
                } else {
1069
                    let user = user_or_key;
1070
                    let username = UserName::from(user);
×
UNCOV
1071
                    match room.try_find_member(&username).map(|m| &m.user) {
×
1072
                        Some(user) => auth.remove_operator(user.public_key.clone()),
×
1073
                        None => invalid_users.push(user.to_string()),
×
1074
                    }
1075
                }
1076
            }
1077

UNCOV
1078
            let mut messages = vec![];
×
1079
            if !invalid_keys.is_empty() {
×
1080
                messages.push(format!("Invalid keys: {}", invalid_keys.join(", ")));
×
1081
            }
1082
            if !invalid_users.is_empty() {
×
1083
                messages.push(format!("Invalid users: {}", invalid_users.join(", ")));
×
1084
            }
1085
            if messages.is_empty() {
×
UNCOV
1086
                messages.push(format!("Server operators list is updated!"));
×
1087
            }
1088

1089
            let message = message::System::new(user.into(), messages.join(utils::NEWLINE));
×
1090
            room.send_message(message.into()).await?;
×
1091
        }
UNCOV
1092
        OplistCommand::Load(mode) => {
×
1093
            if *mode == OplistLoadMode::Replace {
×
UNCOV
1094
                auth.clear_operators();
×
1095
            }
1096
            let message: Message = match auth.load_operators() {
×
1097
                Ok(_) => {
UNCOV
1098
                    let body = "Operators keys are up-to-date with the oplist file".to_string();
×
UNCOV
1099
                    message::System::new(user.into(), body).into()
×
1100
                }
UNCOV
1101
                Err(err) => {
×
UNCOV
1102
                    let body = err.to_string();
×
UNCOV
1103
                    message::Error::new(user.into(), body).into()
×
1104
                }
1105
            };
1106
            room.send_message(message).await?;
×
1107
        }
1108
        OplistCommand::Save => {
1109
            let message: Message = match auth.save_operators() {
×
1110
                Ok(_) => {
UNCOV
1111
                    let body = "Oplist file is up-to-date with the operators".to_string();
×
1112
                    message::System::new(user.into(), body).into()
×
1113
                }
UNCOV
1114
                Err(err) => {
×
1115
                    let body = err.to_string();
×
UNCOV
1116
                    message::Error::new(user.into(), body).into()
×
1117
                }
1118
            };
1119
            room.send_message(message).await?;
×
1120
        }
1121
        OplistCommand::Status => {
1122
            let auth = auth;
1123
            let mut messages: Vec<String> = vec![];
×
1124
            let mut online_operators: Vec<String> = vec![];
×
UNCOV
1125
            let mut offline_keys = vec![];
×
1126

1127
            for key in auth.operators() {
×
1128
                if let Some(user) = room
×
1129
                    .members_iter()
1130
                    .map(|(_, m)| &m.user)
×
UNCOV
1131
                    .find(|u| u.public_key == *key)
×
1132
                {
1133
                    online_operators.push(user.username.clone().into());
×
1134
                } else {
1135
                    offline_keys.push(key.fingerprint());
×
1136
                }
1137
            }
1138

UNCOV
1139
            if !online_operators.is_empty() {
×
1140
                messages.push(format!("Online operators: {}", online_operators.join(", ")));
×
1141
            }
1142

1143
            if !offline_keys.is_empty() {
×
UNCOV
1144
                messages.push(format!(
×
1145
                    "Operators offline keys: {}",
1146
                    offline_keys.join(", ")
×
1147
                ));
1148
            }
1149

1150
            let message = message::System::new(user.into(), messages.join(utils::NEWLINE));
×
UNCOV
1151
            room.send_message(message.into()).await?;
×
1152
        }
1153
        OplistCommand::Help => {
UNCOV
1154
            let mut to_display: Vec<&OplistCommand> = OPLIST_COMMANDS
×
1155
                .iter()
UNCOV
1156
                .filter(|cmd| cmd.is_visible())
×
1157
                .collect();
1158

1159
            to_display.sort_by(|a, b| a.cmd().len().cmp(&b.cmd().len()));
×
1160

1161
            let mut help = format!("Available commands: {}", utils::NEWLINE);
×
1162
            help.push_str(&format_commands(to_display));
×
1163

1164
            let message = message::System::new(user.into(), help);
×
1165
            room.send_message(message.into()).await?;
×
1166
        }
1167
    }
1168

1169
    Ok(())
×
1170
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc