fix: check for duplicate email and send confirmation link while updating email
This commit is contained in:
parent
956b94f97b
commit
434436b81f
4 changed files with 130 additions and 19 deletions
|
@ -10,10 +10,12 @@ use super::*;
|
|||
#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq, Ord, PartialOrd, Getters)]
|
||||
pub struct UpdateEmailCommand {
|
||||
new_email: String,
|
||||
username: String,
|
||||
}
|
||||
|
||||
impl UpdateEmailCommand {
|
||||
pub fn new(
|
||||
username: String,
|
||||
new_email: String,
|
||||
supplied_password: String,
|
||||
actual_password_hash: &str,
|
||||
|
@ -24,7 +26,10 @@ impl UpdateEmailCommand {
|
|||
}
|
||||
config.email(&new_email)?;
|
||||
|
||||
Ok(Self { new_email })
|
||||
Ok(Self {
|
||||
username,
|
||||
new_email,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -41,6 +46,7 @@ mod tests {
|
|||
let hashed_password = config.password(password).unwrap();
|
||||
assert_eq!(
|
||||
UpdateEmailCommand::new(
|
||||
username.into(),
|
||||
new_email.clone(),
|
||||
password.into(),
|
||||
&hashed_password,
|
||||
|
@ -53,15 +59,27 @@ mod tests {
|
|||
|
||||
// email is not valid email
|
||||
assert_eq!(
|
||||
UpdateEmailCommand::new(username.into(), password.into(), &hashed_password, &config)
|
||||
.err(),
|
||||
UpdateEmailCommand::new(
|
||||
username.into(),
|
||||
username.into(),
|
||||
password.into(),
|
||||
&hashed_password,
|
||||
&config
|
||||
)
|
||||
.err(),
|
||||
Some(IdentityCommandError::BadEmail)
|
||||
);
|
||||
|
||||
// wrong password
|
||||
assert_eq!(
|
||||
UpdateEmailCommand::new(username.into(), username.into(), &hashed_password, &config)
|
||||
.err(),
|
||||
UpdateEmailCommand::new(
|
||||
username.into(),
|
||||
username.into(),
|
||||
username.into(),
|
||||
&hashed_password,
|
||||
&config
|
||||
)
|
||||
.err(),
|
||||
Some(IdentityCommandError::WrongPassword)
|
||||
);
|
||||
}
|
||||
|
|
|
@ -7,11 +7,11 @@ use serde::{Deserialize, Serialize};
|
|||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, Getters, Eq, PartialEq, Ord, PartialOrd)]
|
||||
pub struct EmailUpdatedEvent {
|
||||
email: String,
|
||||
new_email: String,
|
||||
}
|
||||
|
||||
impl EmailUpdatedEvent {
|
||||
pub fn new(email: String) -> Self {
|
||||
Self { email }
|
||||
pub fn new(new_email: String) -> Self {
|
||||
Self { new_email }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,10 +9,11 @@ pub mod service;
|
|||
use super::errors::*;
|
||||
|
||||
#[async_trait::async_trait]
|
||||
pub trait UpdateEmailUseCase {
|
||||
pub trait UpdateEmailUseCase: Send + Sync {
|
||||
async fn update_email(
|
||||
&self,
|
||||
cmd: command::UpdateEmailCommand,
|
||||
//) -> errors::ProcessAuthorizationServiceResult<String>;
|
||||
) -> events::EmailUpdatedEvent;
|
||||
) -> IdentityResult<events::EmailUpdatedEvent>;
|
||||
}
|
||||
pub type UpdateEmailServiceObj = std::sync::Arc<dyn UpdateEmailUseCase>;
|
||||
|
|
|
@ -2,25 +2,70 @@
|
|||
//
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
use super::*;
|
||||
use derive_builder::Builder;
|
||||
|
||||
struct UpdateEmailService;
|
||||
use super::*;
|
||||
use crate::identity::application::port::output::{
|
||||
db::{create_verification_secret::*, email_exists::*},
|
||||
mailer::account_validation_link::*,
|
||||
};
|
||||
use crate::utils::random_string::*;
|
||||
|
||||
use crate::identity::application::services::register_user::service::SECRET_LEN;
|
||||
|
||||
#[derive(Builder)]
|
||||
pub struct UpdateEmailService {
|
||||
db_email_exists_adapter: EmailExistsOutDBPortObj,
|
||||
// TODO: update email must use special PURPOSE
|
||||
// TODO: User must have email_verified field
|
||||
db_create_verification_secret_adapter: CreateVerificationSecretOutDBPortObj,
|
||||
mailer_account_validation_link_adapter: AccountValidationLinkOutMailerPortObj,
|
||||
random_string_adapter: GenerateRandomStringInterfaceObj,
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl UpdateEmailUseCase for UpdateEmailService {
|
||||
async fn update_email(
|
||||
&self,
|
||||
cmd: command::UpdateEmailCommand,
|
||||
//) -> errors::ProcessAuthorizationServiceResult<String>;
|
||||
) -> events::EmailUpdatedEvent {
|
||||
// TODO: check if email exists in DB
|
||||
events::EmailUpdatedEvent::new(cmd.new_email().into())
|
||||
) -> IdentityResult<events::EmailUpdatedEvent> {
|
||||
if self
|
||||
.db_email_exists_adapter
|
||||
.email_exists(cmd.new_email())
|
||||
.await
|
||||
.unwrap()
|
||||
{
|
||||
return Err(IdentityError::DuplicateEmail);
|
||||
}
|
||||
|
||||
let secret = self.random_string_adapter.get_random(SECRET_LEN);
|
||||
|
||||
self.db_create_verification_secret_adapter
|
||||
.create_verification_secret(
|
||||
CreateSecretMsgBuilder::default()
|
||||
.secret(secret.clone())
|
||||
.username(cmd.username().into())
|
||||
.build()
|
||||
.unwrap(),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
self.mailer_account_validation_link_adapter
|
||||
.account_validation_link(cmd.new_email(), cmd.username(), &secret)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
Ok(events::EmailUpdatedEvent::new(cmd.new_email().into()))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::utils::random_string::tests::*;
|
||||
|
||||
use crate::tests::bdd::*;
|
||||
|
||||
#[actix_rt::test]
|
||||
async fn test_service() {
|
||||
|
@ -31,6 +76,7 @@ mod tests {
|
|||
let hashed_password = config.password(password).unwrap();
|
||||
|
||||
let cmd = command::UpdateEmailCommand::new(
|
||||
username.into(),
|
||||
new_email.clone(),
|
||||
password.into(),
|
||||
&hashed_password,
|
||||
|
@ -38,8 +84,54 @@ mod tests {
|
|||
)
|
||||
.unwrap();
|
||||
|
||||
let s = UpdateEmailService;
|
||||
let res = s.update_email(cmd.clone()).await;
|
||||
assert_eq!(res.email(), cmd.new_email());
|
||||
// happy case
|
||||
{
|
||||
let s = UpdateEmailServiceBuilder::default()
|
||||
.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.update_email(cmd.clone()).await.unwrap();
|
||||
assert_eq!(res.new_email(), cmd.new_email());
|
||||
}
|
||||
|
||||
// email exists
|
||||
{
|
||||
let s = UpdateEmailServiceBuilder::default()
|
||||
.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.update_email(cmd.clone()).await.err(),
|
||||
Some(IdentityError::DuplicateEmail)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue