diff --git a/src/backend/db_structures.rs b/src/backend/db_structures.rs index c3fa7a1..fab0a98 100644 --- a/src/backend/db_structures.rs +++ b/src/backend/db_structures.rs @@ -8,4 +8,5 @@ pub struct ActivationTokensRow { pub struct UsersRow { pub userid: String, pub password: String, + pub permissions: u16, } diff --git a/src/backend/mod.rs b/src/backend/mod.rs index 597fc8a..2a72f52 100644 --- a/src/backend/mod.rs +++ b/src/backend/mod.rs @@ -1,5 +1,7 @@ mod db_structures; pub mod error; +mod permissions; +mod user; use crate::backend::error::AccountRegisterError; use crate::config::Config; @@ -11,6 +13,7 @@ use argon2::{ use db_structures::{ActivationTokensRow, UsersRow}; use log::info; use sqlx::{types::chrono::Utc, MySqlPool}; +use user::User; use uuid::Uuid; #[derive(Debug, Clone)] @@ -24,8 +27,9 @@ impl Backend { sqlx::query!( r#"CREATE TABLE IF NOT EXISTS Users ( - userid UUID NOT NULL PRIMARY KEY, - password VARCHAR(128) NOT NULL + userid UUID NOT NULL PRIMARY KEY, + password VARCHAR(128) NOT NULL, + permissions SMALLINT UNSIGNED NOT NULL );"# ) .execute(&pool) @@ -90,7 +94,7 @@ impl Backend { } } - async fn get_user(&self, userid: Uuid) -> Result, sqlx::Error> { + async fn get_user(&self, userid: Uuid) -> Result> { match sqlx::query_as!( UsersRow, r#"SELECT * FROM Users WHERE userid = ?;"#, @@ -101,9 +105,9 @@ impl Backend { { Err(e) => match e { sqlx::Error::RowNotFound => Ok(None), - _ => Err(e), + _ => Err(e.into()), }, - Ok(_row) => Ok(Some(())), + Ok(row) => Ok(Some(row.try_into()?)), } } @@ -126,7 +130,36 @@ impl Backend { let userid = Uuid::new_v4(); sqlx::query!( - r#"INSERT INTO Users VALUES (?, ?);"#, + r#"INSERT INTO Users VALUES (?, ?, 0);"#, + userid.as_bytes().as_slice(), + hash + ) + .execute(&self.pool) + .await?; + + Ok(Ok(userid)) + } + + pub async fn create_activation_token( + &self, + token: String, + password: String, + ) -> Result> { + if !self.check_activation_token(&token).await? { + return Ok(Err(AccountRegisterError::InvalidToken)); + } + + let salt = SaltString::generate(&mut OsRng); + + let hash = Argon2::default() + .hash_password(password.as_bytes(), &salt) + .map_err(|_| anyhow::Error::msg("Failed to hash the password"))? + .to_string(); + + let userid = Uuid::new_v4(); + + sqlx::query!( + r#"INSERT INTO Users VALUES (?, ?, 0);"#, userid.as_bytes().as_slice(), hash ) diff --git a/src/backend/permissions.rs b/src/backend/permissions.rs new file mode 100644 index 0000000..73f3b87 --- /dev/null +++ b/src/backend/permissions.rs @@ -0,0 +1,13 @@ +pub enum Permission { + GenerateInviteTokens, + PromoteUsers, +} + +impl Permission { + pub fn as_u16(&self) -> u16 { + match self { + Permission::GenerateInviteTokens => 1, + Permission::PromoteUsers => 1 << 1, + } + } +} diff --git a/src/backend/user.rs b/src/backend/user.rs new file mode 100644 index 0000000..b2e5477 --- /dev/null +++ b/src/backend/user.rs @@ -0,0 +1,40 @@ +use crate::backend::db_structures::UsersRow; +use crate::backend::{permissions::Permission, Backend}; +use anyhow::Result; +use std::str::FromStr; +use uuid::Uuid; + +pub struct User { + uuid: Uuid, + password: String, + permissions: u16, +} + +impl TryFrom for User { + type Error = anyhow::Error; + fn try_from(value: UsersRow) -> std::result::Result { + Ok(Self { + uuid: Uuid::from_str(value.userid.as_str())?, + password: value.password, + permissions: value.permissions, + }) + } +} + +impl User { + pub fn has_permission(&self, permission: Permission) -> bool { + (self.permissions & permission.as_u16()) > 0 + } + + async fn flush(&self, backend: &Backend) -> Result<()> { + sqlx::query!( + r#"UPDATE Users SET password = ?, permissions = ? WHERE userid = ?;"#, + self.password, + self.permissions, + self.uuid.as_bytes().as_slice() + ) + .execute(&backend.pool) + .await?; + Ok(()) + } +}