From 5b3d174edc159952daf02169442d8d8a743d9cad Mon Sep 17 00:00:00 2001 From: projectmoon Date: Wed, 26 May 2021 15:28:59 +0000 Subject: [PATCH] Separate registering and linking accounts. Can register an account with the bot to manage variables and stuff in private room, and then separately "link" it with a password, which makes it available to anything using the bot API (aka web app). Can also unlink and unregister. Check command no longer validates password. It just checks and reports your account status. --- src/commands/management.rs | 131 ++++++++++++++++++++++++++++++++----- src/commands/mod.rs | 4 +- src/commands/parser.rs | 4 +- src/context.rs | 2 +- src/db/sqlite/users.rs | 1 - src/error.rs | 3 + 6 files changed, 124 insertions(+), 21 deletions(-) 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)]