feat: identity: register_user service
This commit is contained in:
parent
8c136cb46c
commit
4b5f8733ce
3 changed files with 166 additions and 19 deletions
|
@ -1,11 +1,18 @@
|
|||
use derive_getters::Getters;
|
||||
// SPDX-FileCopyrightText: 2024 Aravinth Manivannan <realaravinth@batsense.net>
|
||||
//
|
||||
// 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,
|
||||
}
|
||||
|
|
|
@ -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<String>;
|
||||
) -> events::UserRegisteredEvent;
|
||||
) -> IdentityResult<events::UserRegisteredEvent>;
|
||||
}
|
||||
|
|
|
@ -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<String>;
|
||||
) -> events::UserRegisteredEvent {
|
||||
) -> IdentityResult<events::UserRegisteredEvent> {
|
||||
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;
|
||||
// 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)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue