From 12998bbb7849a94c7fc57926d23a2e560dcc473c Mon Sep 17 00:00:00 2001 From: wassim Date: Thu, 31 Oct 2024 12:28:10 +0100 Subject: [PATCH] email handling and sht --- .../controllers/AccountController.java | 9 ++- .../usermanagement/entities/Account.java | 6 ++ .../entities/EmailVerificationToken.java | 6 +- .../events/listeners/EmailsListener.java | 57 ++++++++++++++++--- .../emails/AccountCreatedEvent.java | 14 +++++ .../emails/AccountDeletedEvent.java | 15 +++++ .../emails/PasswordChangedEvent.java | 14 +++++ .../interfaces/services/IAccountService.java | 1 + .../services/AccountService.java | 45 +++++++++++++-- .../EmailVerificationTokenService.java | 2 +- 10 files changed, 150 insertions(+), 19 deletions(-) create mode 100644 src/main/java/com/example/usermanagement/events/publishers/emails/AccountCreatedEvent.java create mode 100644 src/main/java/com/example/usermanagement/events/publishers/emails/AccountDeletedEvent.java create mode 100644 src/main/java/com/example/usermanagement/events/publishers/emails/PasswordChangedEvent.java diff --git a/src/main/java/com/example/usermanagement/controllers/AccountController.java b/src/main/java/com/example/usermanagement/controllers/AccountController.java index 51a8569..9ad980d 100644 --- a/src/main/java/com/example/usermanagement/controllers/AccountController.java +++ b/src/main/java/com/example/usermanagement/controllers/AccountController.java @@ -68,6 +68,13 @@ public ResponseEntity updateMe(@RequestBody UpdateAccountDTO return new ResponseEntity<>(new GeneralAccountDTO(account), HttpStatus.OK); } + // with password + @DeleteMapping("/me") + public ResponseEntity deleteMe(@RequestParam String password) { + accountService.deleteMyAccount(password); + return new ResponseEntity<>("Account deleted", HttpStatus.OK); + } + @GetMapping("/{accountId}") public ResponseEntity getAccount(@PathVariable UUID accountId) { var account = new DetailedAccountDTO(accountService.getAccountById(accountId)); @@ -86,7 +93,7 @@ public ResponseEntity> editAuthorities(@Req public ResponseEntity verifyEmail(@RequestParam String token) { String email = emailVerificationTokenService.consumeEmailVerificationToken(token); accountService.verifyAccountEmail(email); - return new ResponseEntity<>("Email verified", HttpStatus.OK); + return new ResponseEntity<>(email + " verified", HttpStatus.OK); } @PostMapping("/verify-email/resend") diff --git a/src/main/java/com/example/usermanagement/entities/Account.java b/src/main/java/com/example/usermanagement/entities/Account.java index ddf22a1..899d69a 100644 --- a/src/main/java/com/example/usermanagement/entities/Account.java +++ b/src/main/java/com/example/usermanagement/entities/Account.java @@ -44,6 +44,12 @@ public class Account { @Column(nullable = false, name = "is_member", columnDefinition = "boolean default false") private Boolean isMember = false; + @OneToOne(mappedBy = "account", fetch = FetchType.LAZY, cascade = CascadeType.ALL) + private PasswordResetToken passwordResetToken; + + @OneToOne(mappedBy = "account", fetch = FetchType.LAZY, cascade = CascadeType.ALL) + private EmailVerificationToken emailVerificationToken; + @ManyToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL) @JoinTable( name = "account_roles", diff --git a/src/main/java/com/example/usermanagement/entities/EmailVerificationToken.java b/src/main/java/com/example/usermanagement/entities/EmailVerificationToken.java index c9b56a2..50cb6dc 100644 --- a/src/main/java/com/example/usermanagement/entities/EmailVerificationToken.java +++ b/src/main/java/com/example/usermanagement/entities/EmailVerificationToken.java @@ -22,13 +22,13 @@ public class EmailVerificationToken { @Column(nullable = false, unique = true) private String token; - @Column(nullable = false) + @Column(nullable = false,name = "expiry_date") private Instant expiryDate; - @Column(nullable = false) + @Column(nullable = false,name = "created_date") private Instant createdDate; - @Column(nullable = false) + @Column(nullable = false,name = "is_used") private boolean isUsed; @OneToOne(fetch = FetchType.LAZY) diff --git a/src/main/java/com/example/usermanagement/events/listeners/EmailsListener.java b/src/main/java/com/example/usermanagement/events/listeners/EmailsListener.java index 6d234e9..bd594c3 100644 --- a/src/main/java/com/example/usermanagement/events/listeners/EmailsListener.java +++ b/src/main/java/com/example/usermanagement/events/listeners/EmailsListener.java @@ -1,9 +1,7 @@ package com.example.usermanagement.events.listeners; -import com.example.usermanagement.events.publishers.emails.EmailVerificationTokenGeneratedEvent; -import com.example.usermanagement.events.publishers.emails.PasswordHaveBeenResetedEvent; -import com.example.usermanagement.events.publishers.emails.PasswordResetGeneratedEvent; +import com.example.usermanagement.events.publishers.emails.*; import com.example.usermanagement.interfaces.services.IEmailService; import lombok.RequiredArgsConstructor; import org.springframework.beans.factory.annotation.Value; @@ -21,23 +19,66 @@ public class EmailsListener { private final IEmailService emailService; + @EventListener(AccountCreatedEvent.class) + public void onAccountCreatedEvent(AccountCreatedEvent event) { + String email = event.getEmail(); + String body = "Hello, " + email + "\n" + + "Your account have been created successfully." + "\n" + + "If you didn't request this, please contact us."; + + emailService.sendEmail(event.getEmail(), "Account created", body); + } + @EventListener(EmailVerificationTokenGeneratedEvent.class) public void onEmailVerificationTokenGeneratedEvent(EmailVerificationTokenGeneratedEvent event) { - String link = amwc + "/api/accounts/verify-email?token=" + event.getToken(); - String body = "Click here to verify your email: " + link; + String link = amwc + "/auth/confirm-email-verification?token=" + event.getToken(); + String email = event.getEmail(); + String body = "Hello, " + email + "\n" + + "Click here to verify your email: " + link + "\n" + + "If you didn't request this, please ignore this email."; + emailService.sendEmail("wa55death405@gmail.com", "Email verification", body); } @EventListener(PasswordResetGeneratedEvent.class) public void onPasswordResetGeneratedEvent(PasswordResetGeneratedEvent event) { - String link = amwc + "/api/accounts/reset-password?token=" + event.getToken(); - String body = "Click here to reset your password: " + link; + String link = amwc + "/auth/confirm-reset-password?token=" + event.getToken(); + String email = event.getEmail(); + String body = "Hello, " + email + "\n" + + "Click here to reset your password: " + link + "\n" + + "If you didn't request this, please ignore this email."; + emailService.sendEmail(event.getEmail(), "Password reset", body); } @EventListener(PasswordHaveBeenResetedEvent.class) public void onPasswordHaveBeenResetedEvent(PasswordHaveBeenResetedEvent event) { - String body = "Your password have been reseted"; + String email = event.getEmail(); + String body = "Hello, " + email + "\n" + + "Your password have been reseted successfully." + "\n" + + "If you didn't request this, please contact us."; + emailService.sendEmail(event.getEmail(), "Password reseted", body); } + + @EventListener(PasswordChangedEvent.class) + public void onPasswordChangedEvent(PasswordChangedEvent event) { + String email = event.getEmail(); + String body = "Hello, " + email + "\n" + + "Your password have been changed successfully." + "\n" + + "If you didn't request this, please contact us."; + + emailService.sendEmail(event.getEmail(), "Password changed", body); + } + + @EventListener(AccountDeletedEvent.class) + public void onAccountDeletedEvent(AccountDeletedEvent event) { + String email = event.getEmail(); + String body = "Hello, " + email + "\n" + + "Your account have been deleted successfully." + "\n" + + "If you didn't request this, please contact us."; + + emailService.sendEmail(event.getEmail(), "Account deleted", body); + } + } diff --git a/src/main/java/com/example/usermanagement/events/publishers/emails/AccountCreatedEvent.java b/src/main/java/com/example/usermanagement/events/publishers/emails/AccountCreatedEvent.java new file mode 100644 index 0000000..1c8b913 --- /dev/null +++ b/src/main/java/com/example/usermanagement/events/publishers/emails/AccountCreatedEvent.java @@ -0,0 +1,14 @@ +package com.example.usermanagement.events.publishers.emails; + +import lombok.Getter; +import org.springframework.context.ApplicationEvent; + +@Getter +public class AccountCreatedEvent extends ApplicationEvent { + private final String email; + + public AccountCreatedEvent(Object source, String email) { + super(source); + this.email = email; + } +} diff --git a/src/main/java/com/example/usermanagement/events/publishers/emails/AccountDeletedEvent.java b/src/main/java/com/example/usermanagement/events/publishers/emails/AccountDeletedEvent.java new file mode 100644 index 0000000..2b3395a --- /dev/null +++ b/src/main/java/com/example/usermanagement/events/publishers/emails/AccountDeletedEvent.java @@ -0,0 +1,15 @@ +package com.example.usermanagement.events.publishers.emails; + +import lombok.Getter; +import org.springframework.context.ApplicationEvent; + +@Getter +public class AccountDeletedEvent extends ApplicationEvent { + private final String email; + + public AccountDeletedEvent(Object source, String email) { + super(source); + this.email = email; + } + +} diff --git a/src/main/java/com/example/usermanagement/events/publishers/emails/PasswordChangedEvent.java b/src/main/java/com/example/usermanagement/events/publishers/emails/PasswordChangedEvent.java new file mode 100644 index 0000000..5c91dd4 --- /dev/null +++ b/src/main/java/com/example/usermanagement/events/publishers/emails/PasswordChangedEvent.java @@ -0,0 +1,14 @@ +package com.example.usermanagement.events.publishers.emails; + +import lombok.Getter; +import org.springframework.context.ApplicationEvent; + +@Getter +public class PasswordChangedEvent extends ApplicationEvent { + private final String email; + + public PasswordChangedEvent(Object source, String email) { + super(source); + this.email = email; + } +} diff --git a/src/main/java/com/example/usermanagement/interfaces/services/IAccountService.java b/src/main/java/com/example/usermanagement/interfaces/services/IAccountService.java index da458ab..dfb7d64 100644 --- a/src/main/java/com/example/usermanagement/interfaces/services/IAccountService.java +++ b/src/main/java/com/example/usermanagement/interfaces/services/IAccountService.java @@ -25,6 +25,7 @@ public interface IAccountService { void verifyIdentity(boolean isVerified, Account account); Account getMyAccount(); + void deleteMyAccount(String password); Account updateMyAccount(UpdateAccountDTO requestBody); void resetPassword(String token, String newPassword); diff --git a/src/main/java/com/example/usermanagement/services/AccountService.java b/src/main/java/com/example/usermanagement/services/AccountService.java index 1d3db77..e67007e 100644 --- a/src/main/java/com/example/usermanagement/services/AccountService.java +++ b/src/main/java/com/example/usermanagement/services/AccountService.java @@ -7,12 +7,11 @@ import com.example.usermanagement.entities.Role; import com.example.usermanagement.entities.Account; import com.example.usermanagement.events.publishers.*; +import com.example.usermanagement.events.publishers.emails.AccountDeletedEvent; import com.example.usermanagement.events.publishers.emails.PasswordHaveBeenResetedEvent; -import com.example.usermanagement.exceptions.ForbiddenException; +import com.example.usermanagement.exceptions.InputValidationException; import com.example.usermanagement.interfaces.services.IAccountService; -import com.example.usermanagement.repositories.PermissionRepository; -import com.example.usermanagement.repositories.RoleRepository; -import com.example.usermanagement.repositories.AccountRepository; +import com.example.usermanagement.repositories.*; import jakarta.persistence.EntityExistsException; import jakarta.persistence.EntityNotFoundException; import jakarta.transaction.Transactional; @@ -36,9 +35,11 @@ public class AccountService implements IAccountService { private final AccountRepository accountRepository; - private final PasswordEncoder passwordEncoder; private final RoleRepository roleRepository; private final PermissionRepository permissionRepository; + private final EmailVerificationTokenRepository emailVerificationTokenRepository; + private final PasswordResetTokenRepository passwordResetTokenRepository; + private final PasswordEncoder passwordEncoder; private final ApplicationEventPublisher eventPublisher; @Override @@ -118,7 +119,7 @@ public void lockAccount(boolean lock, Account account) { public void changeMyPassword(String oldPassword, String newPassword) { Account account = getMyAccount(); if (!passwordEncoder.matches(oldPassword, account.getPassword())) { - throw new ForbiddenException("Old password is incorrect"); + throw new InputValidationException("Old password is incorrect"); } // TODO: count tries and lock account if too many tries @@ -151,6 +152,38 @@ public Account getMyAccount() { return (Account) SecurityContextHolder.getContext().getAuthentication().getPrincipal(); } + @Override + public void deleteMyAccount(String password) { + Account account = getMyAccount(); + + // Validate password + if (!passwordEncoder.matches(password, account.getPassword())) { + throw new InputValidationException("Password is incorrect"); + } + + // Clear associations + account.getRoles().clear(); + account.getPermissions().clear(); + + // Remove and delete associated tokens + if (account.getEmailVerificationToken() != null) { + emailVerificationTokenRepository.delete(account.getEmailVerificationToken()); + account.setEmailVerificationToken(null); + } + + if (account.getPasswordResetToken() != null) { + passwordResetTokenRepository.delete(account.getPasswordResetToken()); + account.setPasswordResetToken(null); + } + + // Delete the account + accountRepository.delete(account); + + // trigger event + var event = new AccountDeletedEvent(this,account.getEmail()); + eventPublisher.publishEvent(event); + } + @Override public Account updateMyAccount(UpdateAccountDTO requestBody) { Account account = getMyAccount(); diff --git a/src/main/java/com/example/usermanagement/services/EmailVerificationTokenService.java b/src/main/java/com/example/usermanagement/services/EmailVerificationTokenService.java index 82fd0da..23b2aaf 100644 --- a/src/main/java/com/example/usermanagement/services/EmailVerificationTokenService.java +++ b/src/main/java/com/example/usermanagement/services/EmailVerificationTokenService.java @@ -56,7 +56,7 @@ public String generateEmailVerificationToken(Account account) { public String consumeEmailVerificationToken(String token) { var emailVerificationToken = emailVerificationTokenRepository.findByToken(token).orElseThrow( () -> new EntityNotFoundException("Email verification token not found")); - + if(emailVerificationToken.isExpired()) { throw new EmailVerificationTokenExpired("Email verification token has expired"); }