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_builder::Builder;
|
||||||
|
use derive_getters::Getters;
|
||||||
use serde::{Deserialize, Serialize};
|
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 {
|
pub struct UserRegisteredEvent {
|
||||||
username: String,
|
username: String,
|
||||||
email: String,
|
email: String,
|
||||||
hashed_password: String,
|
hashed_password: String,
|
||||||
is_verified: bool,
|
is_verified: bool,
|
||||||
|
is_admin: bool,
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,11 +7,12 @@ pub mod error;
|
||||||
pub mod events;
|
pub mod events;
|
||||||
pub mod service;
|
pub mod service;
|
||||||
|
|
||||||
|
use super::errors::*;
|
||||||
|
|
||||||
#[async_trait::async_trait]
|
#[async_trait::async_trait]
|
||||||
pub trait RegisterUserUseCase {
|
pub trait RegisterUserUseCase: Send + Sync {
|
||||||
async fn register_user(
|
async fn register_user(
|
||||||
&self,
|
&self,
|
||||||
cmd: command::RegisterUserCommand,
|
cmd: command::RegisterUserCommand,
|
||||||
//) -> errors::ProcessAuthorizationServiceResult<String>;
|
) -> IdentityResult<events::UserRegisteredEvent>;
|
||||||
) -> events::UserRegisteredEvent;
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,27 +5,81 @@
|
||||||
// - Hash password
|
// - Hash password
|
||||||
// - Add user record
|
// - Add user record
|
||||||
// - Send verification email
|
// - Send verification email
|
||||||
// - If UID == 0; set admin
|
// - TODO: If UID == 0; set admin
|
||||||
|
use derive_builder::Builder;
|
||||||
|
|
||||||
use super::*;
|
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]
|
#[async_trait::async_trait]
|
||||||
impl RegisterUserUseCase for RegisterUserService {
|
impl RegisterUserUseCase for RegisterUserService {
|
||||||
async fn register_user(
|
async fn register_user(
|
||||||
&self,
|
&self,
|
||||||
cmd: command::RegisterUserCommand,
|
cmd: command::RegisterUserCommand,
|
||||||
//) -> errors::ProcessAuthorizationServiceResult<String>;
|
) -> IdentityResult<events::UserRegisteredEvent> {
|
||||||
) -> 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())
|
.username(cmd.username().into())
|
||||||
.email(cmd.email().into())
|
.email(cmd.email().into())
|
||||||
.hashed_password(cmd.hashed_password().into())
|
.hashed_password(cmd.hashed_password().into())
|
||||||
.is_verified(false)
|
.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 {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
|
use crate::tests::bdd::*;
|
||||||
|
use crate::utils::random_string::tests::*;
|
||||||
|
|
||||||
#[actix_rt::test]
|
#[actix_rt::test]
|
||||||
async fn test_service() {
|
async fn test_service() {
|
||||||
let username = "realaravinth";
|
let username = "realaravinth";
|
||||||
|
@ -45,13 +102,95 @@ mod tests {
|
||||||
password.into(),
|
password.into(),
|
||||||
password.into(),
|
password.into(),
|
||||||
&config,
|
&config,
|
||||||
);
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
let s = RegisterUserService;
|
// happy case
|
||||||
let res = s.register_user(cmd.clone()).await;
|
{
|
||||||
|
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.username(), cmd.username());
|
||||||
assert_eq!(res.email(), cmd.email());
|
assert_eq!(res.email(), cmd.email());
|
||||||
|
assert!(!res.is_admin());
|
||||||
assert!(argon2_creds::Config::verify(res.hashed_password(), password).unwrap())
|
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