From 4b5f8733cedcaa70cf820ceee692c97803698eea Mon Sep 17 00:00:00 2001 From: Aravinth Manivannan Date: Fri, 17 May 2024 23:39:58 +0530 Subject: [PATCH] feat: identity: register_user service --- .../services/register_user/events.rs | 11 +- .../application/services/register_user/mod.rs | 7 +- .../services/register_user/service.rs | 167 ++++++++++++++++-- 3 files changed, 166 insertions(+), 19 deletions(-) diff --git a/src/identity/application/services/register_user/events.rs b/src/identity/application/services/register_user/events.rs index 09acb18..9bc3280 100644 --- a/src/identity/application/services/register_user/events.rs +++ b/src/identity/application/services/register_user/events.rs @@ -1,11 +1,18 @@ -use derive_getters::Getters; +// SPDX-FileCopyrightText: 2024 Aravinth Manivannan +// +// SPDX-License-Identifier: AGPL-3.0-or-later + use derive_builder::Builder; +use derive_getters::Getters; use serde::{Deserialize, Serialize}; -#[derive(Clone, Debug, Builder, Serialize, Deserialize, Getters, Eq, PartialEq, Ord, PartialOrd)] +#[derive( + Clone, Debug, Builder, Serialize, Deserialize, Getters, Eq, PartialEq, Ord, PartialOrd, +)] pub struct UserRegisteredEvent { username: String, email: String, hashed_password: String, is_verified: bool, + is_admin: bool, } diff --git a/src/identity/application/services/register_user/mod.rs b/src/identity/application/services/register_user/mod.rs index 5dc6f6b..079a65e 100644 --- a/src/identity/application/services/register_user/mod.rs +++ b/src/identity/application/services/register_user/mod.rs @@ -7,11 +7,12 @@ pub mod error; pub mod events; pub mod service; +use super::errors::*; + #[async_trait::async_trait] -pub trait RegisterUserUseCase { +pub trait RegisterUserUseCase: Send + Sync { async fn register_user( &self, cmd: command::RegisterUserCommand, - //) -> errors::ProcessAuthorizationServiceResult; - ) -> events::UserRegisteredEvent; + ) -> IdentityResult; } diff --git a/src/identity/application/services/register_user/service.rs b/src/identity/application/services/register_user/service.rs index 44a2b66..2f307a0 100644 --- a/src/identity/application/services/register_user/service.rs +++ b/src/identity/application/services/register_user/service.rs @@ -5,27 +5,81 @@ // - Hash password // - Add user record // - Send verification email -// - If UID == 0; set admin +// - 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::*; -struct RegisterUserService; +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, - //) -> errors::ProcessAuthorizationServiceResult; - ) -> events::UserRegisteredEvent { + ) -> IdentityResult { + if self + .db_username_exists_adapter + .username_exists(cmd.username()) + .await + .unwrap() + { + return Err(IdentityError::UsernameExists); + } - events::UserRegisteredEventBuilder::default() + if self + .db_email_exists_adapter + .email_exists(cmd.email()) + .await + .unwrap() + { + return Err(IdentityError::EmailExists); + } + + let secret = self.random_string_adapter.get_random(SECRET_LEN); + + self.db_create_verification_secret_adapter + .create_verification_secret( + CreateSecretMsgBuilder::default() + .secret(secret.clone()) + .purpose(REGISTRATION_SECRET_PURPOSE.into()) + .username(cmd.username().into()) + .build() + .unwrap(), + ) + .await + .unwrap(); + + self.mailer_account_validation_link_adapter + .account_validation_link(cmd.email(), cmd.username(), &secret) + .await + .unwrap(); + + // TODO: send mail verification link + + Ok(events::UserRegisteredEventBuilder::default() .username(cmd.username().into()) .email(cmd.email().into()) .hashed_password(cmd.hashed_password().into()) .is_verified(false) - .build().unwrap() - + .is_admin(false) // TODO: if UID == 0; set true + .build() + .unwrap()) } } @@ -33,6 +87,9 @@ impl RegisterUserUseCase for RegisterUserService { mod tests { use super::*; + use crate::tests::bdd::*; + use crate::utils::random_string::tests::*; + #[actix_rt::test] async fn test_service() { let username = "realaravinth"; @@ -45,13 +102,95 @@ mod tests { password.into(), password.into(), &config, - ); + ) + .unwrap(); - let s = RegisterUserService; - let res = s.register_user(cmd.clone()).await; - assert_eq!(res.username(), cmd.username()); - assert_eq!(res.email(), cmd.email()); - assert!(argon2_creds::Config::verify(res.hashed_password(), password).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::UsernameExists) + ); + } + + // 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::EmailExists) + ); + } } }