// SPDX-FileCopyrightText: 2024 Aravinth Manivannan // // SPDX-License-Identifier: AGPL-3.0-or-later // - TODO: If UID == 0; set admin use derive_builder::Builder; use super::*; use crate::identity::application::port::output::{ db::{create_verification_secret::*, email_exists::*, username_exists::*}, mailer::account_validation_link::*, }; use crate::utils::random_string::*; pub const SECRET_LEN: usize = 20; pub const REGISTRATION_SECRET_PURPOSE: &str = "account_validation"; #[derive(Builder)] pub struct RegisterUserService { db_email_exists_adapter: EmailExistsOutDBPortObj, db_username_exists_adapter: UsernameExistsOutDBPortObj, db_create_verification_secret_adapter: CreateVerificationSecretOutDBPortObj, mailer_account_validation_link_adapter: AccountValidationLinkOutMailerPortObj, random_string_adapter: GenerateRandomStringInterfaceObj, } #[async_trait::async_trait] impl RegisterUserUseCase for RegisterUserService { async fn register_user( &self, cmd: command::RegisterUserCommand, ) -> IdentityResult { if self .db_username_exists_adapter .username_exists(cmd.username()) .await .unwrap() { return Err(IdentityError::DuplicateUsername); } if self .db_email_exists_adapter .email_exists(cmd.email()) .await .unwrap() { return Err(IdentityError::DuplicateEmail); } let secret = self.random_string_adapter.get_random(SECRET_LEN); self.db_create_verification_secret_adapter .create_verification_secret( CreateSecretMsgBuilder::default() .secret(secret.clone()) .username(cmd.username().into()) .build() .unwrap(), ) .await .unwrap(); self.mailer_account_validation_link_adapter .account_validation_link(cmd.email(), cmd.username(), &secret) .await .unwrap(); Ok(events::UserRegisteredEventBuilder::default() .username(cmd.username().into()) .email(cmd.email().into()) .hashed_password(cmd.hashed_password().into()) .is_verified(false) .email_verified(false) .is_admin(false) // TODO: if UID == 0; set true .build() .unwrap()) } } #[cfg(test)] mod tests { use super::*; use crate::tests::bdd::*; use crate::utils::random_string::tests::*; #[actix_rt::test] async fn test_service() { let username = "realaravinth"; let email = format!("{username}@example.com"); let password = "password"; let config = argon2_creds::Config::default(); let cmd = command::RegisterUserCommand::new( username.into(), email.clone(), password.into(), password.into(), &config, ) .unwrap(); // happy case { let s = RegisterUserServiceBuilder::default() .db_username_exists_adapter(mock_username_exists_db_port( IS_CALLED_ONLY_ONCE, RETURNS_FALSE, )) .db_create_verification_secret_adapter(mock_create_verification_secret_db_port( IS_CALLED_ONLY_ONCE, )) .db_email_exists_adapter(mock_email_exists_db_port( IS_CALLED_ONLY_ONCE, RETURNS_FALSE, )) .random_string_adapter(mock_generate_random_string( IS_CALLED_ONLY_ONCE, RETURNS_RANDOM_STRING.into(), )) .mailer_account_validation_link_adapter(mock_account_validation_link_db_port( IS_CALLED_ONLY_ONCE, )) .build() .unwrap(); let res = s.register_user(cmd.clone()).await.unwrap(); assert_eq!(res.username(), cmd.username()); assert_eq!(res.email(), cmd.email()); assert!(!res.is_admin()); assert!(argon2_creds::Config::verify(res.hashed_password(), password).unwrap()) } // username exists { let s = RegisterUserServiceBuilder::default() .db_username_exists_adapter(mock_username_exists_db_port( IS_CALLED_ONLY_ONCE, RETURNS_TRUE, )) .db_email_exists_adapter(mock_email_exists_db_port(IS_NEVER_CALLED, RETURNS_FALSE)) .db_create_verification_secret_adapter(mock_create_verification_secret_db_port( IS_NEVER_CALLED, )) .random_string_adapter(mock_generate_random_string( IS_NEVER_CALLED, RETURNS_RANDOM_STRING.into(), )) .mailer_account_validation_link_adapter(mock_account_validation_link_db_port( IS_NEVER_CALLED, )) .build() .unwrap(); assert_eq!( s.register_user(cmd.clone()).await.err(), Some(IdentityError::DuplicateUsername) ); } // email exists { let s = RegisterUserServiceBuilder::default() .db_username_exists_adapter(mock_username_exists_db_port( IS_CALLED_ONLY_ONCE, RETURNS_FALSE, )) .db_create_verification_secret_adapter(mock_create_verification_secret_db_port( IS_NEVER_CALLED, )) .db_email_exists_adapter(mock_email_exists_db_port( IS_CALLED_ONLY_ONCE, RETURNS_TRUE, )) .random_string_adapter(mock_generate_random_string( IS_NEVER_CALLED, RETURNS_RANDOM_STRING.into(), )) .mailer_account_validation_link_adapter(mock_account_validation_link_db_port( IS_NEVER_CALLED, )) .build() .unwrap(); assert_eq!( s.register_user(cmd.clone()).await.err(), Some(IdentityError::DuplicateEmail) ); } } }