use crate::constants::{PASSWORD_RESET_TOKEN_LENGTH, PASSWORD_RESET_TOKEN_LIFETIME}; use crate::constants::database_tables_names::{USER_ACCESS_TOKENS_TABLE, USERS_TABLE}; use crate::data::api_client::APIClient; use crate::data::error::{ExecError, ResultBoxError}; use crate::data::new_account::NewAccount; use crate::data::user::UserID; use crate::data::user_token::UserAccessToken; use crate::helpers::{database, user_helper, posts_helper, comments_helper, likes_helper}; use crate::helpers::database::{DeleteQuery, InsertQuery, QueryInfo}; use crate::utils::crypt_utils::{crypt_pass, rand_str}; use crate::utils::date_utils::{mysql_date, time}; use crate::data::account_export::AccountExport; /// Account helper /// /// @author Pierre Hubert /// Create a new account pub fn create(new_account: &NewAccount) -> ResultBoxError { database::InsertQuery::new(USERS_TABLE) .add_str("nom", &new_account.first_name) .add_str("prenom", &new_account.last_name) .add_str("date_creation", &mysql_date()) .add_str("mail", &new_account.email) .add_str("password", &crypt_pass(&new_account.password)?) .insert_drop_result() } /// Attempt to sign-in user /// /// In this version of the api, we consider that there is only one login token required /// This is why I returns just a simple string, the token created for the user in case of success pub fn login_user(email: &str, password: &str, client: &APIClient) -> ResultBoxError { let user = user_helper::find_user_by_email(email)?; // Validate user password let password = crypt_pass(password)?; if !user.password.eq(&password) { return Err(ExecError::boxed_new("The user gave an invalid password!")); } // Check if we already have a login token for this user if let Ok(token) = get_client_tokens(&user.id, client) { return Ok(token.token); } // Create new login tokens let new_token = UserAccessToken { user_id: user.id.clone(), client_id: client.id, token: rand_str(150), }; // Save it database::insert( InsertQuery::new(USER_ACCESS_TOKENS_TABLE) .add_user_id("user_id", &new_token.user_id) .add_u32("service_id", client.id) .add_str("token1", &new_token.token) .add_str("token2", "dummy_data") )?; Ok(new_token.token) } /// Get user login tokens fn get_client_tokens(user_id: &UserID, client: &APIClient) -> ResultBoxError { database::query_row( QueryInfo::new(USER_ACCESS_TOKENS_TABLE) .cond_user_id("user_id", user_id) .cond_u32("service_id", client.id), |res| { Ok(UserAccessToken { user_id: res.get_user_id("user_id")?, client_id: res.get_int64("service_id")? as u32, token: res.get_str("token1")?, }) }, ) } /// Find a user ID based on login token pub fn get_user_by_login_token(token: &str, client: &APIClient) -> ResultBoxError { database::query_row( QueryInfo::new(USER_ACCESS_TOKENS_TABLE) .cond_u32("service_id", client.id) .cond("token1", token) .add_field("user_id"), |res| res.get_user_id("user_id"), ) } /// Check out whether an email address exists or not pub fn exists_mail(mail: &str) -> ResultBoxError { database::QueryInfo::new(USERS_TABLE) .cond("mail", mail) .exec_count() .map(|r| r > 0) } /// Destroy a given user login tokens pub fn destroy_login_tokens(id: &UserID, client: &APIClient) -> ResultBoxError<()> { database::delete(DeleteQuery::new(USER_ACCESS_TOKENS_TABLE) .cond_u32("service_id", client.id) .cond_user_id("user_id", id) )?; Ok(()) } /// Destroy all login tokens of a user pub fn destroy_all_user_tokens(id: &UserID) -> ResultBoxError { database::DeleteQuery::new(USER_ACCESS_TOKENS_TABLE) .cond_user_id("user_id", id) .exec() } /// Generate a new password reset token pub fn generate_password_reset_token(user_id: &UserID) -> ResultBoxError { let token = rand_str(PASSWORD_RESET_TOKEN_LENGTH); database::UpdateInfo::new(USERS_TABLE) .cond_user_id("ID", user_id) .set_str("password_reset_token", &token) .set_u64("password_reset_token_time_create", time()) .exec()?; Ok(token) } /// Remove password reset token for a given user pub fn destroy_password_reset_token_for_user(user_id: &UserID) -> ResultBoxError { database::UpdateInfo::new(USERS_TABLE) .cond_user_id("ID", user_id) .set_str("password_reset_token", "") .set_u64("password_reset_token_time_create", 0) .exec() } /// Get the ID of a user based on a password reset token pub fn get_user_id_from_password_reset_token(token: &str) -> ResultBoxError { database::QueryInfo::new(USERS_TABLE) .cond("password_reset_token", token) .set_custom_where("password_reset_token_time_create > ?") .add_custom_where_argument_u64(time() - PASSWORD_RESET_TOKEN_LIFETIME) .query_row(|r| r.get_user_id("ID")) } /// Check current user's password pub fn check_user_password(user_id: &UserID, password: &str) -> ResultBoxError { let crypt_pass = crypt_pass(password)?; database::QueryInfo::new(USERS_TABLE) .cond_user_id("ID", user_id) .cond("password", &crypt_pass) .exec_count() .map(|r| r > 0) } /// Change the password of a user pub fn change_password(user_id: &UserID, new_password: &String) -> ResultBoxError { database::UpdateInfo::new(USERS_TABLE) .cond_user_id("ID", user_id) .set_str("password", &crypt_pass(new_password)?) .exec() } /// Check out whether a virtual directory is taken by a user or not pub fn check_user_directory_availability(dir: &str, user_id: Option) -> ResultBoxError { let found_user = user_helper::find_user_by_virtual_directory(dir); match (found_user, user_id) { // A user was found, but we did not specify a user (Ok(_), None) => Ok(false), // A user was found, and we specified a user ID, we check if the IDs are the same (Ok(user), Some(id)) => Ok(user.id == id), // No user was found, virtual directory is considered as available (Err(_), _) => Ok(true) } } /// Update the last activity of a user pub fn update_last_activity(user_id: &UserID) -> ResultBoxError { database::UpdateInfo::new(USERS_TABLE) .cond_user_id("ID", user_id) .set_u64("last_activity", time()) .exec() } /// Export an account's data pub fn export(user_id: &UserID) -> ResultBoxError { let data = AccountExport { user: user_helper::find_user_by_id(user_id)?, posts: posts_helper::export_all_posts_user(user_id)?, comments: comments_helper::export_all_user(user_id)?, likes: likes_helper::export_all_user(user_id)? }; Ok(data) }