feat: identity: resend verification email service
Some checks failed
ci/woodpecker/push/woodpecker Pipeline failed
Some checks failed
ci/woodpecker/push/woodpecker Pipeline failed
This commit is contained in:
parent
1be035be98
commit
2170f03cf2
5 changed files with 231 additions and 5 deletions
|
@ -19,6 +19,7 @@ pub enum UserEvent {
|
||||||
PasswordUpdated(PasswordUpdatedEvent),
|
PasswordUpdated(PasswordUpdatedEvent),
|
||||||
EmailUpdated(EmailUpdatedEvent),
|
EmailUpdated(EmailUpdatedEvent),
|
||||||
UserVerified,
|
UserVerified,
|
||||||
|
VerificationEmailResent,
|
||||||
UserPromotedToAdmin(UserPromotedToAdminEvent),
|
UserPromotedToAdmin(UserPromotedToAdminEvent),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -38,6 +39,7 @@ impl DomainEvent for UserEvent {
|
||||||
UserEvent::EmailUpdated { .. } => "UserUpdatedAccountEmail",
|
UserEvent::EmailUpdated { .. } => "UserUpdatedAccountEmail",
|
||||||
UserEvent::UserVerified => "UserIsVerified",
|
UserEvent::UserVerified => "UserIsVerified",
|
||||||
UserEvent::UserPromotedToAdmin { .. } => "UserPromotedToAdmin",
|
UserEvent::UserPromotedToAdmin { .. } => "UserPromotedToAdmin",
|
||||||
|
UserEvent::VerificationEmailResent => "VerficationEmailResent",
|
||||||
};
|
};
|
||||||
|
|
||||||
e.to_string()
|
e.to_string()
|
||||||
|
|
|
@ -5,20 +5,21 @@
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
mod delete_user;
|
mod delete_user;
|
||||||
mod login;
|
|
||||||
mod register_user;
|
|
||||||
mod update_email;
|
|
||||||
mod update_password;
|
|
||||||
//mod resend_verification_email
|
|
||||||
pub mod errors;
|
pub mod errors;
|
||||||
pub mod events;
|
pub mod events;
|
||||||
|
mod login;
|
||||||
mod mark_user_verified;
|
mod mark_user_verified;
|
||||||
|
mod register_user;
|
||||||
|
mod resend_verification_email;
|
||||||
mod set_user_admin;
|
mod set_user_admin;
|
||||||
|
mod update_email;
|
||||||
|
mod update_password;
|
||||||
|
|
||||||
use delete_user::command::*;
|
use delete_user::command::*;
|
||||||
use login::command::*;
|
use login::command::*;
|
||||||
use mark_user_verified::command::*;
|
use mark_user_verified::command::*;
|
||||||
use register_user::command::*;
|
use register_user::command::*;
|
||||||
|
use resend_verification_email::command::*;
|
||||||
use set_user_admin::command::*;
|
use set_user_admin::command::*;
|
||||||
use update_email::command::*;
|
use update_email::command::*;
|
||||||
use update_password::command::*;
|
use update_password::command::*;
|
||||||
|
@ -32,4 +33,5 @@ pub enum UserCommand {
|
||||||
UpdateEmail(UpdateEmailCommand),
|
UpdateEmail(UpdateEmailCommand),
|
||||||
MarkUserVerified(MarkUserVerifiedCommand),
|
MarkUserVerified(MarkUserVerifiedCommand),
|
||||||
SetAdmin(SetAdminCommand),
|
SetAdmin(SetAdminCommand),
|
||||||
|
ResendVerificationEmail(ResendVerificationEmailCommand),
|
||||||
}
|
}
|
||||||
|
|
|
@ -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(_))
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
|
@ -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<()>;
|
||||||
|
}
|
|
@ -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)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue