feat: identity: register_user service

This commit is contained in:
Aravinth Manivannan 2024-05-17 23:39:58 +05:30
parent 8c136cb46c
commit 4b5f8733ce
Signed by: realaravinth
GPG key ID: F8F50389936984FF
3 changed files with 166 additions and 19 deletions

View file

@ -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,
}

View file

@ -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>;
}

View file

@ -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)
);
}
}
}