use super::{parsing::parse_username, user_error}; use super::{UResult, UserVerb, UserVerbRef, VerbContext}; use crate::models::{ item::{Item, Pronouns}, user::User, }; use ansi::ansi; use async_trait::async_trait; use chrono::Utc; use once_cell::sync::OnceCell; use std::collections::HashSet; use tokio::time; pub fn is_invalid_username(name: &str) -> bool { static INVALID_PREFIXES: OnceCell> = OnceCell::new(); static INVALID_SUFFIXES: OnceCell> = OnceCell::new(); static INVALID_WORDS: OnceCell> = OnceCell::new(); let invalid_prefixes = INVALID_PREFIXES.get_or_init(|| vec!["admin", "god", "helper", "npc", "corpse", "dead"]); let invalid_suffixes = INVALID_SUFFIXES.get_or_init(|| vec!["bot"]); let invalid_words = INVALID_WORDS.get_or_init(|| { HashSet::from([ "corp", "to", "from", "dog", "bot", "for", "against", "on", "privileges", "as", "treasury", ]) }); if invalid_words.contains(name) { return true; } for pfx in invalid_prefixes.iter() { if name.starts_with(pfx) { return true; } } for sfx in invalid_suffixes.iter() { if name.ends_with(sfx) { return true; } } false } pub struct Verb; #[async_trait] impl UserVerb for Verb { async fn handle( self: &Self, ctx: &mut VerbContext, _verb: &str, remaining: &str, ) -> UResult<()> { let (username, password, email) = match parse_username(remaining) { Err(e) => user_error("Invalid username: ".to_owned() + e)?, Ok((username, rest)) => match rest.split_whitespace().collect::>()[..] { [password, email] => (username, password, email), [] | [_] => user_error( "Too few options to register - supply username, password, and email".to_owned(), )?, _ => user_error( "Too many options to register - supply username, password, and email" .to_owned(), )?, }, }; if is_invalid_username(&username.to_lowercase()) { user_error("Sorry, that username isn't allowed. Try another".to_owned())?; } if ctx.trans.find_by_username(username).await?.is_some() { user_error("Username already exists".to_owned())?; } if ctx.trans.find_corp_by_name(username).await?.is_some() { user_error("Username clashes with existing corp name".to_owned())?; } if password.len() < 6 { user_error("Password must be 6 characters long or longer".to_owned())?; } else if !validator::validate_email(email) { user_error( "Please supply a valid email in case you need to reset your password.".to_owned(), )?; } let player_item_id = ctx .trans .create_item(&Item { item_type: "player".to_owned(), item_code: username.to_lowercase(), display: username.to_owned(), details: Some("A non-descript individual".to_owned()), location: "room/repro_xv_chargen".to_owned(), pronouns: Pronouns::default_animate(), ..Item::default() }) .await?; // Force a wait to protect against abuse. time::sleep(time::Duration::from_secs(5)).await; let password_hash = bcrypt::hash(password, 10).expect("hash not to fail"); let user_dat = User { username: username.to_owned(), password_hash: password_hash.to_owned(), email: email.to_owned(), player_item_id, registered_at: Some(Utc::now()), ..User::default() }; *ctx.user_dat = Some(user_dat); ctx.trans .queue_for_session( ctx.session, Some(&format!( ansi!("Welcome {}, you are now officially registered.\r\n"), &username )), ) .await?; super::agree::check_and_notify_accepts(ctx).await?; ctx.trans .create_user(ctx.session, ctx.user_dat.as_ref().unwrap()) .await?; Ok(()) } } static VERB_INT: Verb = Verb; pub static VERB: UserVerbRef = &VERB_INT as UserVerbRef;