Do not automatically create accounts; use enum to show this instead. #75

Manually merged
projectmoon merged 2 commits from user-refactor into master 2021-05-26 16:03:10 +00:00
6 changed files with 124 additions and 21 deletions
Showing only changes of commit 5b3d174edc - Show all commits

View File

@ -1,13 +1,13 @@
use super::{Command, Execution, ExecutionResult}; use super::{Command, Execution, ExecutionError, ExecutionResult};
use crate::db::Users; use crate::db::Users;
use crate::error::BotError::{AccountDoesNotExist, AuthenticationError, PasswordCreationError}; use crate::error::BotError::{AccountDoesNotExist, PasswordCreationError};
use crate::logic::hash_password; use crate::logic::hash_password;
use crate::models::{AccountStatus, User}; use crate::models::{AccountStatus, User};
use crate::{context::Context, error::BotError}; use crate::{context::Context, error::BotError};
use async_trait::async_trait; use async_trait::async_trait;
use std::convert::{Into, TryFrom}; use std::convert::{Into, TryFrom};
pub struct RegisterCommand(pub String); pub struct RegisterCommand;
impl From<RegisterCommand> for Box<dyn Command> { impl From<RegisterCommand> for Box<dyn Command> {
fn from(cmd: RegisterCommand) -> Self { fn from(cmd: RegisterCommand) -> Self {
@ -18,8 +18,8 @@ impl From<RegisterCommand> for Box<dyn Command> {
impl TryFrom<&str> for RegisterCommand { impl TryFrom<&str> for RegisterCommand {
type Error = BotError; type Error = BotError;
fn try_from(value: &str) -> Result<Self, Self::Error> { fn try_from(_: &str) -> Result<Self, Self::Error> {
Ok(RegisterCommand(value.to_owned())) Ok(RegisterCommand)
} }
} }
@ -34,24 +34,114 @@ impl Command for RegisterCommand {
} }
async fn execute(&self, ctx: &Context<'_>) -> ExecutionResult { async fn execute(&self, ctx: &Context<'_>) -> ExecutionResult {
let pw_hash = hash_password(&self.0).map_err(|e| PasswordCreationError(e))?; if let Some(_) = ctx.db.get_user(ctx.username).await? {
return Err(ExecutionError(BotError::AccountAlreadyExists));
}
let user = User { let user = User {
username: ctx.username.to_owned(), username: ctx.username.to_owned(),
password: Some(pw_hash), password: None,
account_status: AccountStatus::Registered, account_status: AccountStatus::Registered,
..Default::default() ..Default::default()
}; };
ctx.db.upsert_user(&user).await?; ctx.db.upsert_user(&user).await?;
Execution::success(format!( Execution::success(format!(
"User account registered/updated. Please log in to external applications \ "User account {} registered for bot commands.",
with username {} and the password you set.",
ctx.username ctx.username
)) ))
} }
} }
pub struct CheckCommand(pub String); pub struct UnlinkCommand(pub String);
impl From<UnlinkCommand> for Box<dyn Command> {
fn from(cmd: UnlinkCommand) -> Self {
Box::new(cmd)
}
}
impl TryFrom<&str> for UnlinkCommand {
type Error = BotError;
fn try_from(value: &str) -> Result<Self, Self::Error> {
Ok(UnlinkCommand(value.to_owned()))
}
}
#[async_trait]
impl Command for UnlinkCommand {
fn name(&self) -> &'static str {
"unlink user accountx from external applications"
}
fn is_secure(&self) -> bool {
true
}
async fn execute(&self, ctx: &Context<'_>) -> ExecutionResult {
let mut user = ctx
.db
.get_user(&ctx.username)
.await?
.ok_or(BotError::AccountDoesNotExist)?;
user.password = None;
ctx.db.upsert_user(&user).await?;
Execution::success(format!(
"Accounted {} is now inaccessible to external applications.",
ctx.username
))
}
}
pub struct LinkCommand(pub String);
impl From<LinkCommand> for Box<dyn Command> {
fn from(cmd: LinkCommand) -> Self {
Box::new(cmd)
}
}
impl TryFrom<&str> for LinkCommand {
type Error = BotError;
fn try_from(value: &str) -> Result<Self, Self::Error> {
Ok(LinkCommand(value.to_owned()))
}
}
#[async_trait]
impl Command for LinkCommand {
fn name(&self) -> &'static str {
"link user account to external applications"
}
fn is_secure(&self) -> bool {
true
}
async fn execute(&self, ctx: &Context<'_>) -> ExecutionResult {
let mut user = ctx
.db
.get_user(&ctx.username)
.await?
.ok_or(BotError::AccountDoesNotExist)?;
let pw_hash = hash_password(&self.0).map_err(|e| PasswordCreationError(e))?;
user.password = Some(pw_hash);
ctx.db.upsert_user(&user).await?;
Execution::success(format!(
"Accounted now available for external use. Please log in to \
external applications with username {} and the password you set.",
ctx.username
))
}
}
pub struct CheckCommand;
impl From<CheckCommand> for Box<dyn Command> { impl From<CheckCommand> for Box<dyn Command> {
fn from(cmd: CheckCommand) -> Self { fn from(cmd: CheckCommand) -> Self {
@ -62,15 +152,15 @@ impl From<CheckCommand> for Box<dyn Command> {
impl TryFrom<&str> for CheckCommand { impl TryFrom<&str> for CheckCommand {
type Error = BotError; type Error = BotError;
fn try_from(value: &str) -> Result<Self, Self::Error> { fn try_from(_: &str) -> Result<Self, Self::Error> {
Ok(CheckCommand(value.to_owned())) Ok(CheckCommand)
} }
} }
#[async_trait] #[async_trait]
impl Command for CheckCommand { impl Command for CheckCommand {
fn name(&self) -> &'static str { fn name(&self) -> &'static str {
"check user password" "check user account status"
} }
fn is_secure(&self) -> bool { fn is_secure(&self) -> bool {
@ -78,11 +168,20 @@ impl Command for CheckCommand {
} }
async fn execute(&self, ctx: &Context<'_>) -> ExecutionResult { async fn execute(&self, ctx: &Context<'_>) -> ExecutionResult {
let user = ctx.db.authenticate_user(&ctx.username, &self.0).await?; let user = ctx.db.get_user(&ctx.username).await?;
match user { match user {
Some(_) => Execution::success("Password is correct!".to_string()), Some(user) => match user.password {
None => Err(AuthenticationError.into()), Some(_) => Execution::success(
"Account exists, and is available to external applications with a password. If you forgot your password, change it with !link.".to_string(),
),
None => Execution::success(
"Account exists, but is not available to external applications.".to_string(),
),
},
None => Execution::success(
"No account registered. Only simple commands in public rooms are available.".to_string(),
),
} }
} }
} }

View File

@ -209,7 +209,7 @@ mod tests {
message_body: "!notacommand", message_body: "!notacommand",
}; };
let cmd = RegisterCommand("".to_owned()); let cmd = RegisterCommand;
assert_eq!(execution_allowed(&cmd, &ctx).is_ok(), true); assert_eq!(execution_allowed(&cmd, &ctx).is_ok(), true);
} }
@ -275,7 +275,7 @@ mod tests {
message_body: "!notacommand", message_body: "!notacommand",
}; };
let cmd = RegisterCommand("".to_owned()); let cmd = RegisterCommand;
assert_eq!(execution_allowed(&cmd, &ctx).is_err(), true); assert_eq!(execution_allowed(&cmd, &ctx).is_err(), true);
} }

View File

@ -7,7 +7,7 @@ use crate::commands::{
basic_rolling::RollCommand, basic_rolling::RollCommand,
cofd::PoolRollCommand, cofd::PoolRollCommand,
cthulhu::{CthAdvanceRoll, CthRoll}, cthulhu::{CthAdvanceRoll, CthRoll},
management::{CheckCommand, RegisterCommand, UnregisterCommand}, management::{CheckCommand, LinkCommand, RegisterCommand, UnlinkCommand, UnregisterCommand},
misc::HelpCommand, misc::HelpCommand,
variables::{ variables::{
DeleteVariableCommand, GetAllVariablesCommand, GetVariableCommand, SetVariableCommand, DeleteVariableCommand, GetAllVariablesCommand, GetVariableCommand, SetVariableCommand,
@ -86,6 +86,8 @@ pub fn parse_command(input: &str) -> Result<Box<dyn Command>, BotError> {
"cthadv" | "ctharoll" => convert_to!(CthAdvanceRoll, cmd_input), "cthadv" | "ctharoll" => convert_to!(CthAdvanceRoll, cmd_input),
"help" => convert_to!(HelpCommand, cmd_input), "help" => convert_to!(HelpCommand, cmd_input),
"register" => convert_to!(RegisterCommand, cmd_input), "register" => convert_to!(RegisterCommand, cmd_input),
"link" => convert_to!(LinkCommand, cmd_input),
"unlink" => convert_to!(UnlinkCommand, cmd_input),
"check" => convert_to!(CheckCommand, cmd_input), "check" => convert_to!(CheckCommand, cmd_input),
"unregister" => convert_to!(UnregisterCommand, cmd_input), "unregister" => convert_to!(UnregisterCommand, cmd_input),
_ => Err(CommandParsingError::UnrecognizedCommand(cmd).into()), _ => Err(CommandParsingError::UnrecognizedCommand(cmd).into()),

View File

@ -1,6 +1,6 @@
use crate::db::sqlite::Database; use crate::db::sqlite::Database;
use crate::error::BotError; use crate::error::BotError;
use crate::models::{Account, User}; use crate::models::Account;
use matrix_sdk::identifiers::{RoomId, UserId}; use matrix_sdk::identifiers::{RoomId, UserId};
use matrix_sdk::room::Joined; use matrix_sdk::room::Joined;
use matrix_sdk::Client; use matrix_sdk::Client;

View File

@ -3,7 +3,6 @@ use crate::db::{errors::DataError, Users};
use crate::error::BotError; use crate::error::BotError;
use crate::models::User; use crate::models::User;
use async_trait::async_trait; use async_trait::async_trait;
use log::info;
#[async_trait] #[async_trait]
impl Users for Database { impl Users for Database {

View File

@ -87,6 +87,9 @@ pub enum BotError {
#[error("user account does not exist, try registering")] #[error("user account does not exist, try registering")]
AccountDoesNotExist, AccountDoesNotExist,
#[error("user account already exists")]
AccountAlreadyExists,
} }
#[derive(Error, Debug)] #[derive(Error, Debug)]