All checks were successful
		
		
	
	continuous-integration/drone/push Build is passing
				
			
		
			
				
	
	
		
			243 lines
		
	
	
		
			7.6 KiB
		
	
	
	
		
			Rust
		
	
	
	
	
	
			
		
		
	
	
			243 lines
		
	
	
		
			7.6 KiB
		
	
	
	
		
			Rust
		
	
	
	
	
	
| //! # Users service
 | |
| 
 | |
| use crate::app_config::AppConfig;
 | |
| use crate::connections::db_connection;
 | |
| use crate::constants::{ACCOUNT_DELETE_TOKEN_DURATION, PASSWORD_RESET_TOKEN_DURATION};
 | |
| use crate::models::{NewUser, User, UserID};
 | |
| use crate::schema::users;
 | |
| use crate::services::{families_service, login_token_service, mail_service};
 | |
| use crate::utils::string_utils::rand_str;
 | |
| use crate::utils::time_utils::time;
 | |
| use bcrypt::DEFAULT_COST;
 | |
| use diesel::prelude::*;
 | |
| 
 | |
| /// Get the information of a user, by its id
 | |
| pub async fn get_by_id(id: UserID) -> anyhow::Result<User> {
 | |
|     db_connection::execute(|conn| users::table.filter(users::dsl::id.eq(id.0)).first(conn))
 | |
| }
 | |
| 
 | |
| /// Get the information of a user, by its mail
 | |
| pub async fn get_by_mail(mail: &str) -> anyhow::Result<User> {
 | |
|     db_connection::execute(|conn| users::table.filter(users::dsl::email.eq(mail)).first(conn))
 | |
| }
 | |
| 
 | |
| /// Get the information of a user, by its password reset token
 | |
| pub async fn get_by_pwd_reset_token(token: &str) -> anyhow::Result<User> {
 | |
|     if token.is_empty() {
 | |
|         return Err(anyhow::Error::from(std::io::Error::other(
 | |
|             "Token is empty!",
 | |
|         )));
 | |
|     }
 | |
| 
 | |
|     db_connection::execute(|conn| {
 | |
|         users::table
 | |
|             .filter(
 | |
|                 users::dsl::reset_password_token.eq(token.to_string()).and(
 | |
|                     users::dsl::time_gen_reset_token
 | |
|                         .ge(time() as i64 - PASSWORD_RESET_TOKEN_DURATION.as_secs() as i64),
 | |
|                 ),
 | |
|             )
 | |
|             .first(conn)
 | |
|     })
 | |
| }
 | |
| 
 | |
| /// Get the information of a user, by its account deletion token
 | |
| pub async fn get_by_account_delete_token(token: &str) -> anyhow::Result<User> {
 | |
|     if token.is_empty() {
 | |
|         return Err(anyhow::Error::from(std::io::Error::other(
 | |
|             "Token is empty!",
 | |
|         )));
 | |
|     }
 | |
| 
 | |
|     db_connection::execute(|conn| {
 | |
|         users::table
 | |
|             .filter(
 | |
|                 users::dsl::delete_account_token.eq(token.to_string()).and(
 | |
|                     users::dsl::time_gen_delete_account_token
 | |
|                         .ge(time() as i64 - ACCOUNT_DELETE_TOKEN_DURATION.as_secs() as i64),
 | |
|                 ),
 | |
|             )
 | |
|             .first(conn)
 | |
|     })
 | |
| }
 | |
| 
 | |
| /// Create a new account
 | |
| pub async fn create_account(name: &str, email: &str) -> anyhow::Result<User> {
 | |
|     db_connection::execute(|conn| {
 | |
|         let res = diesel::insert_into(users::table)
 | |
|             .values(&NewUser {
 | |
|                 name: name.trim(),
 | |
|                 email: email.trim(),
 | |
|                 time_create: time() as i64,
 | |
|             })
 | |
|             .get_result(conn)?;
 | |
| 
 | |
|         Ok(res)
 | |
|     })
 | |
| }
 | |
| 
 | |
| /// Check if an email address is already associated with an account
 | |
| pub async fn exists_email(email: &str) -> anyhow::Result<bool> {
 | |
|     db_connection::execute(|conn| {
 | |
|         let count: i64 = users::table
 | |
|             .filter(users::email.eq(email))
 | |
|             .count()
 | |
|             .get_result(conn)?;
 | |
| 
 | |
|         Ok(count != 0)
 | |
|     })
 | |
| }
 | |
| 
 | |
| /// Request password reset
 | |
| pub async fn request_reset_password(user: &mut User) -> anyhow::Result<()> {
 | |
|     // If required, regenerate reset token
 | |
|     if user.reset_password_token.is_none() || user.time_gen_reset_token as u64 + 3600 * 2 < time() {
 | |
|         user.reset_password_token = Some(rand_str(149));
 | |
|         user.time_gen_reset_token = time() as i64;
 | |
| 
 | |
|         update_account(user).await?;
 | |
|     }
 | |
| 
 | |
|     // Send mail
 | |
|     mail_service::send_mail(
 | |
|         &user.email,
 | |
|         "Réinitialisation de votre mot de passe",
 | |
|         format!(
 | |
|             "Bonjour, \n\n\
 | |
|             Vous pouvez réinitialiser le mot de passe de votre compte à l'adresse suivante : {} \n\n\
 | |
|             Ce lien est valide durant 24 heures.\n\n\
 | |
|             Cordialement,\n\n\
 | |
|             L'équipe de GeneIT",
 | |
|             AppConfig::get()
 | |
|                 .get_password_reset_url(user.reset_password_token.as_deref().unwrap_or(""))
 | |
|         ),
 | |
|     )
 | |
|     .await?;
 | |
| 
 | |
|     Ok(())
 | |
| }
 | |
| 
 | |
| /// Request delete account
 | |
| pub async fn request_delete_account(user: &mut User) -> anyhow::Result<()> {
 | |
|     // If required, regenerate token
 | |
|     if user.delete_account_token.is_none()
 | |
|         || user.time_gen_delete_account_token as u64 + 3600 * 2 < time()
 | |
|     {
 | |
|         user.delete_account_token = Some(rand_str(149));
 | |
|         user.time_gen_delete_account_token = time() as i64;
 | |
| 
 | |
|         update_account(user).await?;
 | |
|     }
 | |
| 
 | |
|     // Send mail
 | |
|     mail_service::send_mail(
 | |
|         &user.email,
 | |
|         "Suppression de votre compte",
 | |
|         format!(
 | |
|             "Bonjour, \n\n\
 | |
|             Vous avez demandé la suppression de votre compte GeneIT. Cette opération peut être effectuée via le lien suivant : {} \n\n\
 | |
|             Ce lien est valide durant 12 heures.\n\n\
 | |
|             Cordialement,\n\n\
 | |
|             L'équipe de GeneIT",
 | |
|             AppConfig::get()
 | |
|                 .get_account_delete_url(user.delete_account_token.as_deref().unwrap_or(""))
 | |
|         ),
 | |
|     )
 | |
|         .await?;
 | |
| 
 | |
|     Ok(())
 | |
| }
 | |
| 
 | |
| /// Delete not validated accounts whose reset token has expired
 | |
| pub async fn delete_not_validated_accounts() -> anyhow::Result<()> {
 | |
|     db_connection::execute(|conn| {
 | |
|         diesel::delete(
 | |
|             users::dsl::users.filter(
 | |
|                 users::dsl::time_activate.eq(0).and(
 | |
|                     users::dsl::time_gen_reset_token
 | |
|                         .lt(time() as i64 - PASSWORD_RESET_TOKEN_DURATION.as_secs() as i64),
 | |
|                 ),
 | |
|             ),
 | |
|         )
 | |
|         .execute(conn)?;
 | |
|         Ok(())
 | |
|     })
 | |
| }
 | |
| 
 | |
| /// Delete account
 | |
| pub async fn delete_account(user: &User) -> anyhow::Result<()> {
 | |
|     log::info!("Delete account #{:?}", user.id());
 | |
| 
 | |
|     // Remove families memberships
 | |
|     families_service::remove_all_user_membership(user.id()).await?;
 | |
| 
 | |
|     login_token_service::disconnect_user_from_all_devices(user.id()).await?;
 | |
| 
 | |
|     db_connection::execute(|conn| {
 | |
|         diesel::delete(users::dsl::users.filter(users::dsl::id.eq(user.id().0))).execute(conn)
 | |
|     })?;
 | |
| 
 | |
|     Ok(())
 | |
| }
 | |
| 
 | |
| /// Mark account as validated
 | |
| pub async fn validate_account(user: &mut User) -> anyhow::Result<()> {
 | |
|     if user.time_activate > 0 {
 | |
|         log::debug!(
 | |
|             "Did not activate account {:?} because it is already activated!",
 | |
|             user.id()
 | |
|         );
 | |
|         return Ok(());
 | |
|     }
 | |
| 
 | |
|     user.time_activate = time() as i64;
 | |
| 
 | |
|     update_account(user).await?;
 | |
| 
 | |
|     mail_service::send_mail(
 | |
|         &user.email,
 | |
|         "Activation de votre compte GeneIT",
 | |
|         "Bonjour,\n\n\
 | |
|     Votre compte GeneIT a été activé avec succès !\n\n\
 | |
|     Cordialement,\n\n\
 | |
|     L'équipe de GeneIT",
 | |
|     )
 | |
|     .await?;
 | |
| 
 | |
|     Ok(())
 | |
| }
 | |
| 
 | |
| /// Update account information
 | |
| pub async fn update_account(user: &User) -> anyhow::Result<()> {
 | |
|     db_connection::execute(|conn| {
 | |
|         diesel::update(users::dsl::users.filter(users::dsl::id.eq(user.id().0)))
 | |
|             .set((
 | |
|                 users::dsl::name.eq(user.name.clone()),
 | |
|                 users::dsl::admin.eq(user.admin),
 | |
|                 users::dsl::active.eq(user.active),
 | |
|                 users::dsl::email.eq(user.email.clone()),
 | |
|                 users::dsl::time_gen_reset_token.eq(user.time_gen_reset_token),
 | |
|                 users::dsl::reset_password_token.eq(user.reset_password_token.clone()),
 | |
|                 users::dsl::time_gen_delete_account_token.eq(user.time_gen_delete_account_token),
 | |
|                 users::dsl::delete_account_token.eq(user.delete_account_token.clone()),
 | |
|                 users::dsl::time_activate.eq(user.time_activate),
 | |
|                 users::dsl::password.eq(user.password.clone()),
 | |
|             ))
 | |
|             .execute(conn)
 | |
|     })?;
 | |
| 
 | |
|     Ok(())
 | |
| }
 | |
| 
 | |
| /// Change user paswsord
 | |
| pub async fn change_password(user: &mut User, new_password: &str) -> anyhow::Result<()> {
 | |
|     let hash = bcrypt::hash(new_password, DEFAULT_COST)?;
 | |
| 
 | |
|     user.reset_password_token = None;
 | |
|     user.password = Some(hash);
 | |
| 
 | |
|     update_account(user).await?;
 | |
| 
 | |
|     Ok(())
 | |
| }
 |