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 }