191 lines
6.4 KiB
Rust
191 lines
6.4 KiB
Rust
// SPDX-FileCopyrightText: 2024 Aravinth Manivannan <realaravinth@batsense.net>
|
|
//
|
|
// 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<events::UserRegisteredEvent> {
|
|
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)
|
|
);
|
|
}
|
|
}
|
|
}
|