diff --git a/src/constants.rs b/src/constants.rs index d521872..d944efa 100644 --- a/src/constants.rs +++ b/src/constants.rs @@ -60,6 +60,10 @@ pub mod database_tables_names { /// Forez presence table pub const FOREZ_PRESENCE_TABLE: &str = "forez_presence"; + + /// Administrators tables + pub const ADMIN_LIST_TABLE: &str = "comunic_admin"; + pub const ADMIN_KEYS_TABLE: &str = "comunic_admin_key"; } /// Push Notifications Database prefix @@ -153,6 +157,12 @@ pub const PASSWORD_RESET_TOKEN_LENGTH: usize = 255; /// Duration of the validity of a password reset token (6 hours) pub const PASSWORD_RESET_TOKEN_LIFETIME: u64 = 60 * 60 * 6; +/// Length of admin reset tokens +pub const ADMIN_RESET_TOKEN_LENGTH: usize = 255; + +/// Duration of the validity of a password reset token (1 hour) +pub const ADMIN_RESET_TOKEN_LIFETIME: u64 = 60 * 60; + /// Minimum password length pub const PASSWORD_MIN_LENGTH: usize = 3; diff --git a/src/data/admin.rs b/src/data/admin.rs new file mode 100644 index 0000000..c177d2f --- /dev/null +++ b/src/data/admin.rs @@ -0,0 +1,41 @@ +//! # Comunic administrator +//! +//! @author Pierre Hubert + +#[derive(Copy, Clone, Eq, PartialEq)] +pub struct AdminID(u64); + +impl AdminID { + pub fn new(id: u64) -> Self { + Self(id) + } + + pub fn id(&self) -> u64 { + self.0 + } +} + +pub struct NewAdmin { + pub name: String, + pub email: String, +} + +pub struct AdminResetToken { + pub token: String, + pub expire: u64, +} + +pub struct Admin { + pub id: AdminID, + pub time_create: u64, + pub name: String, + pub email: String, + pub reset_token: Option, +} + +pub struct AdminKey { + pub id: u64, + pub admin_id: AdminID, + pub name: String, + pub key: String, +} \ No newline at end of file diff --git a/src/data/mod.rs b/src/data/mod.rs index 7af769c..599c6df 100644 --- a/src/data/mod.rs +++ b/src/data/mod.rs @@ -40,4 +40,5 @@ pub mod user_ws_connection; pub mod call_signal; pub mod new_notifications_settings; pub mod push_notification; -pub mod presence; \ No newline at end of file +pub mod presence; +pub mod admin; \ No newline at end of file diff --git a/src/helpers/admin_account_helper.rs b/src/helpers/admin_account_helper.rs new file mode 100644 index 0000000..8a03f95 --- /dev/null +++ b/src/helpers/admin_account_helper.rs @@ -0,0 +1,73 @@ +//! # Administration accounts helper +//! +//! @author Pierre Hubert + +use crate::constants::{ADMIN_RESET_TOKEN_LENGTH, ADMIN_RESET_TOKEN_LIFETIME}; +use crate::constants::database_tables_names::ADMIN_LIST_TABLE; +use crate::data::admin::{Admin, AdminID, AdminResetToken, NewAdmin}; +use crate::data::error::{ExecError, Res}; +use crate::helpers::database; +use crate::utils::crypt_utils::rand_str; +use crate::utils::date_utils::time; + +/// Create a new admin account +pub fn create(new_admin: &NewAdmin) -> Res { + if find_admin_by_email(&new_admin.email).is_ok() { + return Err(ExecError::boxed_new("An other admin account already holds the same email address!")); + } + + database::InsertQuery::new(ADMIN_LIST_TABLE) + .add_u64("time_create", time()) + .add_str("name", &new_admin.name) + .add_str("email", &new_admin.email) + .insert_expect_result() + .map(|i| AdminID::new(i)) +} + + +/// Get admin information by admin email address +pub fn find_admin_by_email(email: &str) -> Res { + database::QueryInfo::new(ADMIN_LIST_TABLE) + .cond("email", email) + .query_row(db_to_admin) +} + +/// Create a new reset token for an admin +pub fn create_new_reset_token(id: AdminID) -> Res { + let token = AdminResetToken { + token: rand_str(ADMIN_RESET_TOKEN_LENGTH), + expire: time() + ADMIN_RESET_TOKEN_LIFETIME, + }; + + database::UpdateInfo::new(ADMIN_LIST_TABLE) + .cond_admin_id("id", id) + .set_str("reset_token", &token.token) + .set_u64("reset_token_expire", token.expire) + .exec()?; + + Ok(token) +} + +/// Turn a database entry into an admin structure +fn db_to_admin(row: &database::RowResult) -> Res { + let reset_token_expire = row.get_optional_u64("reset_token_expire")? + .unwrap_or(0); + + let reset_token = if reset_token_expire < time() { + None + } else { + Some(AdminResetToken { + token: row.get_str("reset_token")?, + expire: row.get_u64("reset_token_expire")?, + }) + }; + + + Ok(Admin { + id: row.get_admin_id("id")?, + time_create: row.get_u64("time_create")?, + name: row.get_str("name")?, + email: row.get_str("email")?, + reset_token, + }) +} \ No newline at end of file diff --git a/src/helpers/database.rs b/src/helpers/database.rs index ab5a28f..00ca049 100644 --- a/src/helpers/database.rs +++ b/src/helpers/database.rs @@ -8,6 +8,7 @@ use chrono::{TimeZone, Utc}; use mysql::{Binary, Pool, ResultSet, Value}; use mysql::prelude::Queryable; +use crate::data::admin::AdminID; use crate::data::config::{conf, DatabaseConfig}; use crate::data::conversation::ConvID; use crate::data::error::{ExecError, ResultBoxError}; @@ -376,6 +377,11 @@ impl<'a> RowResult<'a> { Ok(UserID::new(self.get_u64(name)?)) } + /// Get the ID of an admin included in the request + pub fn get_admin_id(&self, name: &str) -> ResultBoxError { + Ok(AdminID::new(self.get_u64(name)?)) + } + /// Get the ID of a group included in the response pub fn get_group_id(&self, name: &str) -> ResultBoxError { Ok(GroupID::new(self.get_u64(name)?)) @@ -903,6 +909,12 @@ impl UpdateInfo { self } + /// Filter with an admin id + pub fn cond_admin_id(mut self, name: &str, val: AdminID) -> Self { + self.cond.insert(name.to_string(), Value::UInt(val.id())); + self + } + /// Filter with a group id pub fn cond_group_id(mut self, name: &str, val: &GroupID) -> UpdateInfo { self.cond.insert(name.to_string(), Value::UInt(val.id())); diff --git a/src/helpers/mod.rs b/src/helpers/mod.rs index d7c0e25..49f2b42 100644 --- a/src/helpers/mod.rs +++ b/src/helpers/mod.rs @@ -21,4 +21,5 @@ pub mod calls_helper; pub mod push_notifications_helper; pub mod independent_push_notifications_service_helper; pub mod firebase_notifications_helper; -pub mod forez_presence_helper; \ No newline at end of file +pub mod forez_presence_helper; +pub mod admin_account_helper; \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index c7e3cb1..d9f6692 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,8 +1,9 @@ use comunic_server::{cleanup_thread, server}; +use comunic_server::data::admin::NewAdmin; use comunic_server::data::config::{conf, Config}; use comunic_server::data::error::Res; use comunic_server::data::user::UserID; -use comunic_server::helpers::{account_helper, database}; +use comunic_server::helpers::{account_helper, admin_account_helper, database}; use comunic_server::utils::date_utils::current_year; type MainActionFunction = Res; @@ -38,7 +39,15 @@ fn get_actions() -> Vec { description: "Create a password reset URL for a user".to_string(), arguments: vec!["user_id".to_string()], function: Box::new(reset_password), - } + }, + + // Create a new administrator + Action { + name: "create_admin".to_string(), + description: "Create a new administrator account".to_string(), + arguments: vec!["name".to_string(), "email".to_string()], + function: Box::new(create_admin), + }, ] } @@ -142,5 +151,24 @@ fn reset_password(args: Vec) -> Res { println!("{}", conf().password_reset_url.replace("{TOKEN}", &token)); + Ok(()) +} + +fn create_admin(args: Vec) -> Res { + let new_admin = NewAdmin { + name: args[0].to_string(), + email: args[1].to_string(), + }; + + if !mailchecker::is_valid(&new_admin.email) { + eprintln!("Specified email address is not valid!"); + std::process::exit(-1); + } + + let id = admin_account_helper::create(&new_admin) + .expect("Failed to create account!"); + + println!("* New admin ID: {}", id.id()); + Ok(()) } \ No newline at end of file