diff --git a/src/commands/management.rs b/src/commands/management.rs index ab84029..975949a 100644 --- a/src/commands/management.rs +++ b/src/commands/management.rs @@ -1,13 +1,13 @@ -use super::{Command, Execution, ExecutionResult}; +use super::{Command, Execution, ExecutionError, ExecutionResult}; use crate::db::Users; -use crate::error::BotError::{AccountDoesNotExist, AuthenticationError, PasswordCreationError}; +use crate::error::BotError::{AccountDoesNotExist, PasswordCreationError}; use crate::logic::hash_password; use crate::models::{AccountStatus, User}; use crate::{context::Context, error::BotError}; use async_trait::async_trait; use std::convert::{Into, TryFrom}; -pub struct RegisterCommand(pub String); +pub struct RegisterCommand; impl From for Box { fn from(cmd: RegisterCommand) -> Self { @@ -18,8 +18,8 @@ impl From for Box { impl TryFrom<&str> for RegisterCommand { type Error = BotError; - fn try_from(value: &str) -> Result { - Ok(RegisterCommand(value.to_owned())) + fn try_from(_: &str) -> Result { + Ok(RegisterCommand) } } @@ -34,24 +34,114 @@ impl Command for RegisterCommand { } 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 { username: ctx.username.to_owned(), - password: Some(pw_hash), + password: None, account_status: AccountStatus::Registered, ..Default::default() }; ctx.db.upsert_user(&user).await?; Execution::success(format!( - "User account registered/updated. Please log in to external applications \ - with username {} and the password you set.", + "User account {} registered for bot commands.", ctx.username )) } } -pub struct CheckCommand(pub String); +pub struct UnlinkCommand(pub String); + +impl From for Box { + fn from(cmd: UnlinkCommand) -> Self { + Box::new(cmd) + } +} + +impl TryFrom<&str> for UnlinkCommand { + type Error = BotError; + + fn try_from(value: &str) -> Result { + 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 for Box { + fn from(cmd: LinkCommand) -> Self { + Box::new(cmd) + } +} + +impl TryFrom<&str> for LinkCommand { + type Error = BotError; + + fn try_from(value: &str) -> Result { + 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 for Box { fn from(cmd: CheckCommand) -> Self { @@ -62,15 +152,15 @@ impl From for Box { impl TryFrom<&str> for CheckCommand { type Error = BotError; - fn try_from(value: &str) -> Result { - Ok(CheckCommand(value.to_owned())) + fn try_from(_: &str) -> Result { + Ok(CheckCommand) } } #[async_trait] impl Command for CheckCommand { fn name(&self) -> &'static str { - "check user password" + "check user account status" } fn is_secure(&self) -> bool { @@ -78,11 +168,20 @@ impl Command for CheckCommand { } 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 { - Some(_) => Execution::success("Password is correct!".to_string()), - None => Err(AuthenticationError.into()), + Some(user) => match user.password { + 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(), + ), } } } diff --git a/src/commands/mod.rs b/src/commands/mod.rs index 52bd5c4..937f7ff 100644 --- a/src/commands/mod.rs +++ b/src/commands/mod.rs @@ -209,7 +209,7 @@ mod tests { message_body: "!notacommand", }; - let cmd = RegisterCommand("".to_owned()); + let cmd = RegisterCommand; assert_eq!(execution_allowed(&cmd, &ctx).is_ok(), true); } @@ -275,7 +275,7 @@ mod tests { message_body: "!notacommand", }; - let cmd = RegisterCommand("".to_owned()); + let cmd = RegisterCommand; assert_eq!(execution_allowed(&cmd, &ctx).is_err(), true); } diff --git a/src/commands/parser.rs b/src/commands/parser.rs index 48ed9fa..7ea3a16 100644 --- a/src/commands/parser.rs +++ b/src/commands/parser.rs @@ -7,7 +7,7 @@ use crate::commands::{ basic_rolling::RollCommand, cofd::PoolRollCommand, cthulhu::{CthAdvanceRoll, CthRoll}, - management::{CheckCommand, RegisterCommand, UnregisterCommand}, + management::{CheckCommand, LinkCommand, RegisterCommand, UnlinkCommand, UnregisterCommand}, misc::HelpCommand, variables::{ DeleteVariableCommand, GetAllVariablesCommand, GetVariableCommand, SetVariableCommand, @@ -86,6 +86,8 @@ pub fn parse_command(input: &str) -> Result, BotError> { "cthadv" | "ctharoll" => convert_to!(CthAdvanceRoll, cmd_input), "help" => convert_to!(HelpCommand, 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), "unregister" => convert_to!(UnregisterCommand, cmd_input), _ => Err(CommandParsingError::UnrecognizedCommand(cmd).into()), diff --git a/src/context.rs b/src/context.rs index 7cd5837..4dfe669 100644 --- a/src/context.rs +++ b/src/context.rs @@ -1,6 +1,6 @@ use crate::db::sqlite::Database; use crate::error::BotError; -use crate::models::{Account, User}; +use crate::models::Account; use matrix_sdk::identifiers::{RoomId, UserId}; use matrix_sdk::room::Joined; use matrix_sdk::Client; diff --git a/src/db/sqlite/users.rs b/src/db/sqlite/users.rs index ac6e819..71c07f3 100644 --- a/src/db/sqlite/users.rs +++ b/src/db/sqlite/users.rs @@ -3,7 +3,6 @@ use crate::db::{errors::DataError, Users}; use crate::error::BotError; use crate::models::User; use async_trait::async_trait; -use log::info; #[async_trait] impl Users for Database { diff --git a/src/error.rs b/src/error.rs index 46de875..4161608 100644 --- a/src/error.rs +++ b/src/error.rs @@ -87,6 +87,9 @@ pub enum BotError { #[error("user account does not exist, try registering")] AccountDoesNotExist, + + #[error("user account already exists")] + AccountAlreadyExists, } #[derive(Error, Debug)]