feat: identity: resend verification email service
Some checks failed
ci/woodpecker/push/woodpecker Pipeline failed

This commit is contained in:
Aravinth Manivannan 2024-05-18 19:10:47 +05:30
parent 1be035be98
commit 2170f03cf2
Signed by: realaravinth
GPG key ID: F8F50389936984FF
5 changed files with 231 additions and 5 deletions

View file

@ -19,6 +19,7 @@ pub enum UserEvent {
PasswordUpdated(PasswordUpdatedEvent),
EmailUpdated(EmailUpdatedEvent),
UserVerified,
VerificationEmailResent,
UserPromotedToAdmin(UserPromotedToAdminEvent),
}
@ -38,6 +39,7 @@ impl DomainEvent for UserEvent {
UserEvent::EmailUpdated { .. } => "UserUpdatedAccountEmail",
UserEvent::UserVerified => "UserIsVerified",
UserEvent::UserPromotedToAdmin { .. } => "UserPromotedToAdmin",
UserEvent::VerificationEmailResent => "VerficationEmailResent",
};
e.to_string()

View file

@ -5,20 +5,21 @@
use serde::{Deserialize, Serialize};
mod delete_user;
mod login;
mod register_user;
mod update_email;
mod update_password;
//mod resend_verification_email
pub mod errors;
pub mod events;
mod login;
mod mark_user_verified;
mod register_user;
mod resend_verification_email;
mod set_user_admin;
mod update_email;
mod update_password;
use delete_user::command::*;
use login::command::*;
use mark_user_verified::command::*;
use register_user::command::*;
use resend_verification_email::command::*;
use set_user_admin::command::*;
use update_email::command::*;
use update_password::command::*;
@ -32,4 +33,5 @@ pub enum UserCommand {
UpdateEmail(UpdateEmailCommand),
MarkUserVerified(MarkUserVerifiedCommand),
SetAdmin(SetAdminCommand),
ResendVerificationEmail(ResendVerificationEmailCommand),
}

View file

@ -0,0 +1,58 @@
// SPDX-FileCopyrightText: 2024 Aravinth Manivannan <realaravinth@batsense.net>
//
// SPDX-License-Identifier: AGPL-3.0-or-later
use super::*;
use derive_getters::Getters;
use serde::{Deserialize, Serialize};
#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq, Ord, PartialOrd, Getters)]
pub struct ResendVerificationEmailCommand {
username: String,
email: String,
}
impl ResendVerificationEmailCommand {
pub fn new(
username: String,
email: String,
config: &argon2_creds::Config,
) -> IdentityCommandResult<Self> {
let username = config.username(&username)?;
config.email(&email)?;
Ok(Self { username, email })
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_cmd() {
let config = argon2_creds::Config::default();
ResendVerificationEmailCommand::new(
"realaravinth".into(),
"realaravinth@example.com".into(),
&config,
)
.unwrap();
assert_eq!(
ResendVerificationEmailCommand::new("realaravinth".into(), "username".into(), &config,)
.err(),
Some(IdentityCommandError::BadEmail)
);
assert!(matches!(
ResendVerificationEmailCommand::new(
"username".into(),
"username@example.com".into(),
&config,
)
.err(),
Some(IdentityCommandError::BadUsername(_))
));
}
}

View file

@ -0,0 +1,12 @@
pub mod command;
pub mod service;
use super::errors::*;
#[async_trait::async_trait]
pub trait ResendVerificationEmailUseCase: Send + Sync {
async fn resend_verification_email(
&self,
cmd: command::ResendVerificationEmailCommand,
) -> IdentityResult<()>;
}

View file

@ -0,0 +1,152 @@
// SPDX-FileCopyrightText: 2024 Aravinth Manivannan <realaravinth@batsense.net>
//
// SPDX-License-Identifier: AGPL-3.0-or-later
use derive_builder::Builder;
use super::*;
use crate::identity::application::port::output::{
db::{email_exists::*, get_verification_secret::*, username_exists::*},
mailer::account_validation_link::*,
};
#[derive(Builder)]
pub struct ResendVerificationEmailService {
db_email_exists_adapter: EmailExistsOutDBPortObj,
db_username_exists_adapter: UsernameExistsOutDBPortObj,
db_get_verification_secret_adapter: GetVerificationSecretOutDBPortObj,
mailer_account_validation_link_adapter: AccountValidationLinkOutMailerPortObj,
}
#[async_trait::async_trait]
impl ResendVerificationEmailUseCase for ResendVerificationEmailService {
async fn resend_verification_email(
&self,
cmd: command::ResendVerificationEmailCommand,
) -> IdentityResult<()> {
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
.db_get_verification_secret_adapter
.get_verification_secret(cmd.username())
.await
.unwrap();
self.mailer_account_validation_link_adapter
.account_validation_link(cmd.email(), cmd.username(), &secret)
.await
.unwrap();
Ok(())
}
}
#[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 secret = "asdfasdf";
let config = argon2_creds::Config::default();
let cmd =
command::ResendVerificationEmailCommand::new(username.into(), email.clone(), &config)
.unwrap();
// happy case
{
let s = ResendVerificationEmailServiceBuilder::default()
.db_username_exists_adapter(mock_username_exists_db_port(
IS_CALLED_ONLY_ONCE,
RETURNS_FALSE,
))
.db_get_verification_secret_adapter(mock_get_verification_secret_db_port(
IS_CALLED_ONLY_ONCE,
secret.into(),
))
.db_email_exists_adapter(mock_email_exists_db_port(
IS_CALLED_ONLY_ONCE,
RETURNS_FALSE,
))
.mailer_account_validation_link_adapter(mock_account_validation_link_db_port(
IS_CALLED_ONLY_ONCE,
))
.build()
.unwrap();
s.resend_verification_email(cmd.clone()).await.unwrap();
}
// username exists
{
let s = ResendVerificationEmailServiceBuilder::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_get_verification_secret_adapter(mock_get_verification_secret_db_port(
IS_NEVER_CALLED,
secret.into(),
))
.mailer_account_validation_link_adapter(mock_account_validation_link_db_port(
IS_NEVER_CALLED,
))
.build()
.unwrap();
assert_eq!(
s.resend_verification_email(cmd.clone()).await.err(),
Some(IdentityError::DuplicateUsername)
);
}
// email exists
{
let s = ResendVerificationEmailServiceBuilder::default()
.db_username_exists_adapter(mock_username_exists_db_port(
IS_CALLED_ONLY_ONCE,
RETURNS_FALSE,
))
.db_get_verification_secret_adapter(mock_get_verification_secret_db_port(
IS_NEVER_CALLED,
secret.into(),
))
.db_email_exists_adapter(mock_email_exists_db_port(
IS_CALLED_ONLY_ONCE,
RETURNS_TRUE,
))
.mailer_account_validation_link_adapter(mock_account_validation_link_db_port(
IS_NEVER_CALLED,
))
.build()
.unwrap();
assert_eq!(
s.resend_verification_email(cmd.clone()).await.err(),
Some(IdentityError::DuplicateEmail)
);
}
}
}