Skip to content

Commit d8d3284

Browse files
committed
added mail server and implemented whats needed for password recovery
1 parent 4437ad1 commit d8d3284

File tree

15 files changed

+345
-50
lines changed

15 files changed

+345
-50
lines changed

pom.xml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,11 @@
8383
<artifactId>spring-boot-starter-validation</artifactId>
8484
</dependency>
8585

86+
<dependency>
87+
<groupId>org.springframework.boot</groupId>
88+
<artifactId>spring-boot-starter-mail</artifactId>
89+
</dependency>
90+
8691
<!-- devtools spring-->
8792
<dependency>
8893
<groupId>org.springframework.boot</groupId>

src/main/java/com/example/usermanagement/controllers/AccountController.java

Lines changed: 56 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,14 @@
22

33
import com.example.usermanagement.dto.accounts.*;
44
import com.example.usermanagement.entities.Account;
5+
import com.example.usermanagement.events.publishers.emails.EmailVerificationTokenGeneratedEvent;
6+
import com.example.usermanagement.events.publishers.emails.PasswordResetGeneratedEvent;
57
import com.example.usermanagement.interfaces.services.IAccountService;
8+
import com.example.usermanagement.interfaces.services.IEmailService;
69
import com.example.usermanagement.interfaces.services.IEmailVerificationTokenService;
7-
import jakarta.persistence.EntityExistsException;
10+
import com.example.usermanagement.interfaces.services.IPasswordResetTokenService;
811
import lombok.RequiredArgsConstructor;
12+
import org.springframework.context.ApplicationEventPublisher;
913
import org.springframework.data.domain.Page;
1014
import org.springframework.http.HttpStatus;
1115
import org.springframework.http.ResponseEntity;
@@ -21,8 +25,11 @@ public class AccountController {
2125

2226
private final IAccountService accountService;
2327
private final IEmailVerificationTokenService emailVerificationTokenService;
28+
private final IPasswordResetTokenService passwordResetTokenService;
29+
private final IEmailService emailService;
30+
private final ApplicationEventPublisher eventPublisher;
2431

25-
// auth related
32+
// account management related
2633
@PostMapping
2734
public ResponseEntity<UUID> createAccount(@RequestBody CreateAccountDTO requestBody) {
2835

@@ -32,49 +39,12 @@ public ResponseEntity<UUID> createAccount(@RequestBody CreateAccountDTO requestB
3239
// generate email verification token
3340
String token = emailVerificationTokenService.generateEmailVerificationToken(userAccount);
3441

35-
// TODO: send email with token
42+
String body = "Click here to verify your email: http://localhost:8080/api/accounts/verify-email?token=" + token;
43+
emailService.sendEmail("[email protected]", "Email verification", body);
3644

3745
return new ResponseEntity<>(userAccount.getId(), HttpStatus.CREATED);
3846
}
3947

40-
@GetMapping("/verify-email")
41-
public ResponseEntity<String> verifyEmail(@RequestParam String token) {
42-
String email = emailVerificationTokenService.consumeEmailVerificationToken(token);
43-
accountService.verifyAccountEmail(email);
44-
return new ResponseEntity<>("Email verified", HttpStatus.OK);
45-
}
46-
47-
// resend email verification token
48-
@PostMapping("/verify-email/resend")
49-
public ResponseEntity<String> resendEmailVerificationToken(@RequestParam String email) {
50-
Account account = accountService.getAccountByEmail(email);
51-
String token = emailVerificationTokenService.generateEmailVerificationToken(account);
52-
53-
// TODO: send email with token
54-
55-
return new ResponseEntity<>("Email verification token sent", HttpStatus.OK);
56-
}
57-
58-
@PostMapping("/reset-password/request")
59-
public ResponseEntity<String> resetPassword(@RequestParam String email) {
60-
Account account = accountService.getAccountByEmail(email);
61-
accountService.requestResetPassword(account);
62-
return new ResponseEntity<>("Password reset", HttpStatus.OK);
63-
}
64-
65-
@PostMapping("/reset-password/confirm")
66-
public ResponseEntity<String> resetPassword(@RequestBody ResetPasswordRequest requestBody) {
67-
accountService.resetPassword(requestBody.getToken(), requestBody.getNewPassword());
68-
return new ResponseEntity<>("Password reset", HttpStatus.OK);
69-
}
70-
71-
@PostMapping("/change-password")
72-
public ResponseEntity<String> changePassword(@RequestBody ChangePasswordRequest requestBody) {
73-
accountService.changeMyPassword(requestBody.getOldPassword(), requestBody.getNewPassword());
74-
return new ResponseEntity<>("Password changed", HttpStatus.OK);
75-
}
76-
77-
// info related
7848
@GetMapping
7949
public ResponseEntity<Page<GeneralAccountDTO>> getAccounts(
8050
@RequestParam(required = false) String email,
@@ -111,6 +81,51 @@ public ResponseEntity<List<AccountAuthoritiesEditResponse>> editAuthorities(@Req
11181
return new ResponseEntity<>(res, HttpStatus.OK);
11282
}
11383

84+
85+
// email verification related
86+
@GetMapping("/verify-email")
87+
public ResponseEntity<String> verifyEmail(@RequestParam String token) {
88+
String email = emailVerificationTokenService.consumeEmailVerificationToken(token);
89+
accountService.verifyAccountEmail(email);
90+
return new ResponseEntity<>("Email verified", HttpStatus.OK);
91+
}
92+
93+
@PostMapping("/verify-email/resend")
94+
public ResponseEntity<String> resendEmailVerificationToken(@RequestParam String email) {
95+
Account account = accountService.getAccountByEmail(email);
96+
String token = emailVerificationTokenService.generateEmailVerificationToken(account);
97+
98+
eventPublisher.publishEvent(new EmailVerificationTokenGeneratedEvent(this, token, email));
99+
100+
return new ResponseEntity<>("Email verification token sent", HttpStatus.OK);
101+
}
102+
103+
104+
// password related
105+
@PostMapping("/reset-password/resend")
106+
public ResponseEntity<String> requestResetPassword(@RequestParam String email) {
107+
Account account = accountService.getAccountByEmail(email);
108+
String token = passwordResetTokenService.generatePasswordResetToken(account);
109+
110+
eventPublisher.publishEvent(new PasswordResetGeneratedEvent(this, email, token));
111+
112+
return new ResponseEntity<>("Password reset token sent", HttpStatus.OK);
113+
}
114+
115+
@PostMapping("/reset-password")
116+
public ResponseEntity<String> confirmResetPassword(@RequestBody ResetPasswordRequest requestBody) {
117+
accountService.resetPassword(requestBody.getToken(), requestBody.getNewPassword());
118+
return new ResponseEntity<>("Password reset", HttpStatus.OK);
119+
}
120+
121+
@PostMapping("/change-password")
122+
public ResponseEntity<String> changePassword(@RequestBody ChangePasswordRequest requestBody) {
123+
accountService.changeMyPassword(requestBody.getOldPassword(), requestBody.getNewPassword());
124+
return new ResponseEntity<>("Password changed", HttpStatus.OK);
125+
}
126+
127+
128+
// special info management related
114129
@PostMapping("/{accountId}/identity-verification")
115130
public ResponseEntity<String> verifyIdentity(@RequestParam boolean verify, @PathVariable UUID accountId) {
116131
Account account = accountService.getAccountById(accountId);
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
package com.example.usermanagement.entities;
2+
3+
import jakarta.persistence.*;
4+
import lombok.Getter;
5+
import lombok.NoArgsConstructor;
6+
import lombok.Setter;
7+
import java.time.Instant;
8+
import java.util.UUID;
9+
10+
@Entity
11+
@Getter
12+
@Setter
13+
@Table(name = "password_reset_tokens")
14+
@NoArgsConstructor
15+
public class PasswordResetToken {
16+
17+
@Id
18+
@GeneratedValue(strategy = GenerationType.UUID)
19+
private UUID id;
20+
21+
@Column(nullable = false, unique = true)
22+
private String token;
23+
24+
@Column(nullable = false, name = "expiry_date")
25+
private Instant expiryDate;
26+
27+
@Column(nullable = false, name = "created_date")
28+
private Instant createdDate;
29+
30+
@Column(nullable = false, name = "is_used")
31+
private Boolean isUsed;
32+
33+
@OneToOne(fetch = FetchType.LAZY)
34+
private Account account;
35+
36+
public PasswordResetToken(Account account, int expiryTimeInSec) {
37+
this.token = UUID.randomUUID().toString();
38+
this.createdDate = Instant.now();
39+
this.expiryDate = this.createdDate.plusSeconds(expiryTimeInSec);
40+
this.isUsed = false;
41+
this.account = account;
42+
}
43+
44+
public boolean isExpired() {
45+
return Instant.now().isAfter(this.expiryDate);
46+
}
47+
48+
public boolean haveBeenCreatedLately(int seconds) {
49+
return Instant.now().isBefore(this.createdDate.plusSeconds(seconds));
50+
}
51+
52+
}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
package com.example.usermanagement.events.listeners;
2+
3+
4+
import com.example.usermanagement.events.publishers.emails.EmailVerificationTokenGeneratedEvent;
5+
import com.example.usermanagement.events.publishers.emails.PasswordHaveBeenResetedEvent;
6+
import com.example.usermanagement.events.publishers.emails.PasswordResetGeneratedEvent;
7+
import com.example.usermanagement.interfaces.services.IEmailService;
8+
import lombok.RequiredArgsConstructor;
9+
import org.springframework.beans.factory.annotation.Value;
10+
import org.springframework.context.event.EventListener;
11+
import org.springframework.stereotype.Component;
12+
13+
// TODO: make it use thymeleaf for email templates
14+
15+
@Component
16+
@RequiredArgsConstructor
17+
public class EmailsListener {
18+
19+
@Value("${app.clients.web.amwc}")
20+
private String amwc;
21+
22+
private final IEmailService emailService;
23+
24+
@EventListener(EmailVerificationTokenGeneratedEvent.class)
25+
public void onEmailVerificationTokenGeneratedEvent(EmailVerificationTokenGeneratedEvent event) {
26+
String link = amwc + "/api/accounts/verify-email?token=" + event.getToken();
27+
String body = "Click here to verify your email: " + link;
28+
emailService.sendEmail("[email protected]", "Email verification", body);
29+
}
30+
31+
@EventListener(PasswordResetGeneratedEvent.class)
32+
public void onPasswordResetGeneratedEvent(PasswordResetGeneratedEvent event) {
33+
String link = amwc + "/api/accounts/reset-password?token=" + event.getToken();
34+
String body = "Click here to reset your password: " + link;
35+
emailService.sendEmail(event.getEmail(), "Password reset", body);
36+
}
37+
38+
@EventListener(PasswordHaveBeenResetedEvent.class)
39+
public void onPasswordHaveBeenResetedEvent(PasswordHaveBeenResetedEvent event) {
40+
String body = "Your password have been reseted";
41+
emailService.sendEmail(event.getEmail(), "Password reseted", body);
42+
}
43+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package com.example.usermanagement.events.publishers.emails;
2+
3+
import lombok.Getter;
4+
import org.springframework.context.ApplicationEvent;
5+
6+
@Getter
7+
public class EmailVerificationTokenGeneratedEvent extends ApplicationEvent{
8+
private final String token;
9+
private final String email;
10+
11+
public EmailVerificationTokenGeneratedEvent(Object source, String token, String email) {
12+
super(source);
13+
this.token = token;
14+
this.email = email;
15+
}
16+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
package com.example.usermanagement.events.publishers.emails;
2+
3+
import lombok.Getter;
4+
import org.springframework.context.ApplicationEvent;
5+
6+
@Getter
7+
public class PasswordHaveBeenResetedEvent extends ApplicationEvent {
8+
private final String email;
9+
10+
public PasswordHaveBeenResetedEvent(Object source, String email) {
11+
super(source);
12+
this.email = email;
13+
}
14+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package com.example.usermanagement.events.publishers.emails;
2+
3+
import lombok.Getter;
4+
import org.springframework.context.ApplicationEvent;
5+
6+
@Getter
7+
public class PasswordResetGeneratedEvent extends ApplicationEvent {
8+
private final String email;
9+
private final String token;
10+
11+
public PasswordResetGeneratedEvent(Object source, String email, String token) {
12+
super(source);
13+
this.email = email;
14+
this.token = token;
15+
}
16+
}

src/main/java/com/example/usermanagement/interfaces/services/IAccountService.java

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,6 @@ public interface IAccountService {
2727
Account getMyAccount();
2828
Account updateMyAccount(UpdateAccountDTO requestBody);
2929

30-
void requestResetPassword(Account account);
3130
void resetPassword(String token, String newPassword);
3231
void changeMyPassword(String oldPassword, String newPassword);
3332
void verifyAccountEmail(String accountEmail);
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
package com.example.usermanagement.interfaces.services;
2+
3+
public interface IEmailService {
4+
void sendEmail(String to, String subject, String body);
5+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
package com.example.usermanagement.interfaces.services;
2+
3+
import com.example.usermanagement.entities.Account;
4+
5+
public interface IPasswordResetTokenService {
6+
String generatePasswordResetToken(Account account);
7+
String consumePasswordResetToken(String token);
8+
}

0 commit comments

Comments
 (0)