forked from projectmoon/tenebrous-dicebot
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.
This commit is contained in:
parent
495df13fe6
commit
5b3d174edc
|
@ -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(),
|
||||||
|
),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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()),
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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)]
|
||||||
|
|
Loading…
Reference in New Issue