/ backend / src / services / create_account_service.rs
create_account_service.rs
  1  use crate::api::core::activate_account::rocket_uri_macro_get_activate_account;
  2  use crate::db::{account_repository, whitelist_repository};
  3  use crate::db::{activation_code_repository, login_details_repository};
  4  use crate::db::{new_transaction, DB};
  5  use crate::models::activation_code::ActivationCode;
  6  use crate::models::login_details::LoginDetails;
  7  use crate::services::email_service::EmailError;
  8  use crate::services::password_service;
  9  use crate::util::accounts_error::AccountsError;
 10  use crate::util::config::Config;
 11  use sqlx::Pool;
 12  
 13  use super::email_service::EmailProvider;
 14  
 15  #[derive(Debug, thiserror::Error)]
 16  pub enum CreateAccountError {
 17      #[error("An internal error occured")]
 18      Internal, // An internal error occurred
 19      #[error("The email is already in use")]
 20      EmailInUse, // The email is already being used
 21      #[error("Email is not in the whitelist")]
 22      EmailNotWhitelisted, // The email is not in the whitelist
 23      #[error("Email error")]
 24      EmailError(#[from] EmailError),
 25  }
 26  
 27  impl From<AccountsError> for CreateAccountError {
 28      fn from(_: AccountsError) -> Self {
 29          CreateAccountError::Internal
 30      }
 31  }
 32  
 33  impl From<sqlx::Error> for CreateAccountError {
 34      fn from(_: sqlx::Error) -> Self {
 35          CreateAccountError::Internal
 36      }
 37  }
 38  
 39  pub async fn create_account(
 40      config: &Config,
 41      email_provider: &EmailProvider,
 42      db_pool: &Pool<DB>,
 43      first_name: String,
 44      last_name: String,
 45      email: String,
 46      password: String,
 47  ) -> Result<(), CreateAccountError> {
 48      let mut transaction = new_transaction(db_pool).await?;
 49  
 50      let existing_with_email =
 51          match login_details_repository::get_by_email(&mut transaction, &email).await {
 52              Ok(val) => val,
 53              Err(err) => {
 54                  error!("DB err: {:?}", err);
 55                  return Err(CreateAccountError::Internal);
 56              }
 57          };
 58  
 59      if existing_with_email.is_some() {
 60          return Err(CreateAccountError::EmailInUse);
 61      }
 62  
 63      match whitelist_repository::get_local_account_by_email(&mut transaction, &email).await {
 64          Ok(Some(_)) => {
 65              // Is whitelisted so all is fine!
 66          }
 67          Ok(None) => {
 68              // Not whitelisted
 69              error!(
 70                  "Cannot create account due to email {} not being whitelisted",
 71                  email
 72              );
 73              return Err(CreateAccountError::EmailNotWhitelisted);
 74          }
 75          Err(err) => {
 76              error!("DB err: {:?}", err);
 77              return Err(CreateAccountError::Internal);
 78          }
 79      };
 80  
 81      let account = match account_repository::insert(&mut transaction, &first_name, &last_name).await
 82      {
 83          Ok(acc) => acc,
 84          Err(err) => {
 85              error!("Failed to create account {:?}", err);
 86              return Err(CreateAccountError::Internal);
 87          }
 88      };
 89  
 90      let (hashed_password, nonces) =
 91          match password_service::hash_and_encrypt_password(password.to_owned(), config) {
 92              Ok(pass) => pass,
 93              Err(err) => {
 94                  error!("Failed to hash and encrypt password: {:?}", err);
 95                  return Err(CreateAccountError::Internal);
 96              }
 97          };
 98  
 99      let unactivated_account = login_details_repository::create_unactivated_account(
100          &mut transaction,
101          &account,
102          &email,
103          &hashed_password,
104          &nonces,
105      )
106      .await
107      .map_err(|err| {
108          error!("Failed to create login details, err: {:?}", err);
109          CreateAccountError::Internal
110      })?;
111  
112      let activation_code =
113          activation_code_repository::insert(&mut transaction, unactivated_account.account_id)
114              .await
115              .map_err(|err| {
116                  error!("Failed to create activation_code, err: {:?}", err);
117                  CreateAccountError::Internal
118              })?;
119  
120      let email_content = format_email_content(config, &unactivated_account, &activation_code);
121  
122      // Send email to the email address for confirmation
123      if let Err(e) = email_provider
124          .send_email(
125              &unactivated_account.email,
126              "Activate your accounts-rs account",
127              // TODO: Make the activation time configurable so that it is correct.
128              &email_content,
129          )
130          .await
131      {
132          error!("Failed to send email, err: {}", e);
133          return Err(CreateAccountError::Internal);
134      }
135  
136      transaction.commit().await?;
137      Ok(())
138  }
139  
140  fn format_email_content(
141      config: &Config,
142      unactivated_account: &LoginDetails,
143      activation_code: &ActivationCode,
144  ) -> String {
145      let activate_account_uri = format!(
146          "{}/api/core{}",
147          config.backend_address,
148          uri!(get_activate_account(
149              Some(unactivated_account.email.clone()),
150              Some(activation_code.code.to_string())
151          ))
152          .to_string(),
153      );
154  
155      format!(
156          r#"Hi!
157  
158  An account has been created for accounts-rs with this email but it must be activated before use.
159  To activate the account, go to the following address: {activate_account_uri}.
160  
161  If the account is not activated within 12 hours it will be deleted.
162  
163  If you did not register to accounts-rs, please ignore this email!
164          "#,
165          activate_account_uri = activate_account_uri,
166      )
167  }