Skip to content

Commit 8e55f76

Browse files
Add Update pwd API & Mail template
1 parent f319cc0 commit 8e55f76

File tree

11 files changed

+474
-11
lines changed

11 files changed

+474
-11
lines changed

src/main/java/com/accolite/pru/health/AuthApp/advice/AuthControllerAdvice.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import com.accolite.pru.health.AuthApp.exception.MailSendException;
77
import com.accolite.pru.health.AuthApp.exception.ResourceAlreadyInUseException;
88
import com.accolite.pru.health.AuthApp.exception.ResourceNotFoundException;
9+
import com.accolite.pru.health.AuthApp.exception.UpdatePasswordException;
910
import com.accolite.pru.health.AuthApp.exception.UserLoginException;
1011
import com.accolite.pru.health.AuthApp.exception.UserRegistrationException;
1112
import com.accolite.pru.health.AuthApp.model.payload.ApiResponse;
@@ -176,5 +177,15 @@ public ApiResponse handleInvalidTokenException(InvalidTokenRequestException ex)
176177
return apiResponse;
177178
}
178179

180+
@ExceptionHandler(value = UpdatePasswordException.class)
181+
@ResponseStatus(HttpStatus.EXPECTATION_FAILED)
182+
@ResponseBody
183+
public ApiResponse handleUpdatePasswordException(UpdatePasswordException ex) {
184+
ApiResponse apiResponse = new ApiResponse();
185+
apiResponse.setSuccess(false);
186+
apiResponse.setData(ex.getMessage());
187+
return apiResponse;
188+
}
189+
179190

180191
}
Lines changed: 40 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,28 @@
11
package com.accolite.pru.health.AuthApp.controller;
22

33
import com.accolite.pru.health.AuthApp.annotation.CurrentUser;
4-
import com.accolite.pru.health.AuthApp.exception.ResourceNotFoundException;
4+
import com.accolite.pru.health.AuthApp.event.OnUserAccountChangeEvent;
5+
import com.accolite.pru.health.AuthApp.exception.UpdatePasswordException;
56
import com.accolite.pru.health.AuthApp.model.CustomUserDetails;
67
import com.accolite.pru.health.AuthApp.model.User;
78
import com.accolite.pru.health.AuthApp.model.payload.ApiResponse;
9+
import com.accolite.pru.health.AuthApp.model.payload.UpdatePasswordRequest;
810
import com.accolite.pru.health.AuthApp.service.AuthService;
911
import com.accolite.pru.health.AuthApp.service.UserService;
1012
import org.apache.log4j.Logger;
1113
import org.springframework.beans.factory.annotation.Autowired;
14+
import org.springframework.context.ApplicationEventPublisher;
1215
import org.springframework.http.ResponseEntity;
1316
import org.springframework.security.access.prepost.PreAuthorize;
1417
import org.springframework.web.bind.annotation.GetMapping;
18+
import org.springframework.web.bind.annotation.PostMapping;
19+
import org.springframework.web.bind.annotation.RequestBody;
1520
import org.springframework.web.bind.annotation.RequestMapping;
1621
import org.springframework.web.bind.annotation.RequestParam;
1722
import org.springframework.web.bind.annotation.RestController;
1823

24+
import javax.validation.Valid;
25+
1926
@RestController
2027
@RequestMapping("/api/user")
2128
public class UserController {
@@ -28,37 +35,63 @@ public class UserController {
2835
@Autowired
2936
private UserService userService;
3037

38+
@Autowired
39+
private ApplicationEventPublisher applicationEventPublisher;
40+
41+
/**
42+
* Checks is a given email is in use or not.
43+
*/
3144
@GetMapping("/checkEmailInUse")
3245
public ResponseEntity<?> checkEmailInUse(@RequestParam("email") String email) {
3346
Boolean emailExists = authService.emailAlreadyExists(email);
3447
return ResponseEntity.ok(new ApiResponse(emailExists.toString(), true));
3548
}
3649

50+
/**
51+
* Checks is a given username is in use or not.
52+
*/
3753
@GetMapping("/checkUsernameInUse")
3854
public ResponseEntity<?> checkUsernameInUse(@RequestParam("username") String username) {
3955
Boolean usernameExists = authService.usernameAlreadyExists(username);
4056
return ResponseEntity.ok(new ApiResponse(usernameExists.toString(), true));
4157
}
4258

59+
/**
60+
* Gets the current user profile of the logged in user
61+
*/
4362
@GetMapping("/me")
63+
@PreAuthorize("USER")
4464
public ResponseEntity<?> getUserProfile(@CurrentUser CustomUserDetails currentUser) {
4565
logger.info("Inside secured resource with user");
4666
logger.info(currentUser.getEmail() + " has role: " + currentUser.getRoles());
4767
return ResponseEntity.ok("Hello. This is about me");
4868
}
4969

70+
/**
71+
* Returns all admins in the system. Requires Admin access
72+
*/
5073
@GetMapping("/admins")
5174
@PreAuthorize("hasRole('ADMIN')")
5275
public ResponseEntity<?> getAllAdmins() {
5376
logger.info("Inside secured resource with admin");
5477
return ResponseEntity.ok("Hello. This is about admins");
5578
}
5679

57-
@GetMapping()
58-
public ResponseEntity<?> getUser(@RequestParam("email") String email) {
59-
User user = userService.findByEmail(email)
60-
.orElseThrow(() -> new ResourceNotFoundException("User", "email", email));
61-
return ResponseEntity.ok(user);
62-
}
6380

81+
/**
82+
* Updates the password of the current logged in user
83+
*/
84+
@PostMapping("/password/update")
85+
@PreAuthorize("hasRole('USER')")
86+
public ResponseEntity<?> updateUserPassword(@CurrentUser CustomUserDetails customUserDetails,
87+
@Valid @RequestBody UpdatePasswordRequest updatePasswordRequest) {
88+
User updatedUser = authService.updatePassword(customUserDetails, updatePasswordRequest)
89+
.orElseThrow(() -> new UpdatePasswordException("--Empty--", "No such user present."));
90+
91+
OnUserAccountChangeEvent onUserPasswordChangeEvent =
92+
new OnUserAccountChangeEvent(updatedUser, "Update Password", "Changed successful");
93+
applicationEventPublisher.publishEvent(onUserPasswordChangeEvent);
94+
95+
return ResponseEntity.ok(new ApiResponse("Password changed successfully", true));
96+
}
6497
}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
package com.accolite.pru.health.AuthApp.event;
2+
3+
import com.accolite.pru.health.AuthApp.model.User;
4+
import org.springframework.context.ApplicationEvent;
5+
6+
public class OnUserAccountChangeEvent extends ApplicationEvent {
7+
8+
private User user;
9+
private String action;
10+
private String actionStatus;
11+
12+
public OnUserAccountChangeEvent(User user, String action, String actionStatus) {
13+
super(user);
14+
this.user = user;
15+
this.action = action;
16+
this.actionStatus = actionStatus;
17+
}
18+
19+
public User getUser() {
20+
return user;
21+
}
22+
23+
public void setUser(User user) {
24+
this.user = user;
25+
}
26+
27+
public String getAction() {
28+
return action;
29+
}
30+
31+
public void setAction(String action) {
32+
this.action = action;
33+
}
34+
35+
public String getActionStatus() {
36+
return actionStatus;
37+
}
38+
39+
public void setActionStatus(String actionStatus) {
40+
this.actionStatus = actionStatus;
41+
}
42+
}
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
package com.accolite.pru.health.AuthApp.event.listener;
2+
3+
import com.accolite.pru.health.AuthApp.event.OnUserAccountChangeEvent;
4+
import com.accolite.pru.health.AuthApp.exception.MailSendException;
5+
import com.accolite.pru.health.AuthApp.model.User;
6+
import com.accolite.pru.health.AuthApp.service.MailService;
7+
import freemarker.template.TemplateException;
8+
import org.apache.log4j.Logger;
9+
import org.springframework.beans.factory.annotation.Autowired;
10+
import org.springframework.context.ApplicationListener;
11+
import org.springframework.scheduling.annotation.Async;
12+
import org.springframework.stereotype.Component;
13+
14+
import javax.mail.MessagingException;
15+
import java.io.IOException;
16+
17+
@Component
18+
public class OnUserAccountChangeListener implements ApplicationListener<OnUserAccountChangeEvent> {
19+
20+
@Autowired
21+
private MailService mailService;
22+
23+
private static final Logger logger = Logger.getLogger(OnUserAccountChangeListener.class);
24+
25+
/**
26+
* As soon as a registration event is complete, invoke the email verification
27+
* asynchronously in an another thread pool
28+
*/
29+
@Override
30+
@Async
31+
public void onApplicationEvent(OnUserAccountChangeEvent onUserAccountChangeEvent) {
32+
sendAccountChangeEmail(onUserAccountChangeEvent);
33+
}
34+
35+
/**
36+
* Send email verification to the user and persist the token in the database.
37+
*/
38+
private void sendAccountChangeEmail(OnUserAccountChangeEvent event) {
39+
User user = event.getUser();
40+
String action = event.getAction();
41+
String actionStatus = event.getActionStatus();
42+
String recipientAddress = user.getEmail();
43+
44+
try {
45+
mailService.sendAccountChangeEmail(action, actionStatus, recipientAddress);
46+
} catch (IOException | TemplateException | MessagingException e) {
47+
logger.error(e);
48+
throw new MailSendException(recipientAddress, "Account Change Mail");
49+
}
50+
}
51+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
package com.accolite.pru.health.AuthApp.exception;
2+
3+
import org.springframework.http.HttpStatus;
4+
import org.springframework.web.bind.annotation.ResponseStatus;
5+
6+
@ResponseStatus(HttpStatus.EXPECTATION_FAILED)
7+
public class UpdatePasswordException extends RuntimeException {
8+
9+
private String user;
10+
private String message;
11+
12+
public UpdatePasswordException(String user, String message) {
13+
super(String.format("Couldn't update password for [%s]: [%s])", user, message));
14+
this.user = user;
15+
this.message = message;
16+
}
17+
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
package com.accolite.pru.health.AuthApp.model.payload;
2+
3+
import javax.validation.constraints.NotBlank;
4+
5+
public class UpdatePasswordRequest {
6+
7+
@NotBlank(message = "Old password must not be blank")
8+
private String oldPassword;
9+
10+
@NotBlank(message = "New password must not be blank")
11+
private String newPassword;
12+
13+
public UpdatePasswordRequest(@NotBlank String oldPassword, @NotBlank String newPassword) {
14+
this.oldPassword = oldPassword;
15+
this.newPassword = newPassword;
16+
}
17+
18+
public UpdatePasswordRequest() {
19+
}
20+
21+
public String getOldPassword() {
22+
return oldPassword;
23+
}
24+
25+
public void setOldPassword(String oldPassword) {
26+
this.oldPassword = oldPassword;
27+
}
28+
29+
public String getNewPassword() {
30+
return newPassword;
31+
}
32+
33+
public void setNewPassword(String newPassword) {
34+
this.newPassword = newPassword;
35+
}
36+
}

src/main/java/com/accolite/pru/health/AuthApp/service/AuthService.java

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,15 @@
44
import com.accolite.pru.health.AuthApp.exception.InvalidTokenRequestException;
55
import com.accolite.pru.health.AuthApp.exception.ResourceAlreadyInUseException;
66
import com.accolite.pru.health.AuthApp.exception.ResourceNotFoundException;
7+
import com.accolite.pru.health.AuthApp.exception.UpdatePasswordException;
8+
import com.accolite.pru.health.AuthApp.model.CustomUserDetails;
79
import com.accolite.pru.health.AuthApp.model.Role;
810
import com.accolite.pru.health.AuthApp.model.RoleName;
911
import com.accolite.pru.health.AuthApp.model.TokenStatus;
1012
import com.accolite.pru.health.AuthApp.model.User;
1113
import com.accolite.pru.health.AuthApp.model.payload.LoginRequest;
1214
import com.accolite.pru.health.AuthApp.model.payload.RegistrationRequest;
15+
import com.accolite.pru.health.AuthApp.model.payload.UpdatePasswordRequest;
1316
import com.accolite.pru.health.AuthApp.model.token.EmailVerificationToken;
1417
import com.accolite.pru.health.AuthApp.util.Util;
1518
import org.apache.log4j.Logger;
@@ -164,4 +167,28 @@ public Optional<EmailVerificationToken> recreateRegistrationToken(String existin
164167
}
165168
return emailVerificationTokenOpt.map(emailVerificationTokenService::updateExistingTokenWithNameAndExpiry);
166169
}
170+
171+
/**
172+
* Validates the password of the current logged in user with the given password
173+
*/
174+
public Boolean currentPasswordMatches(User currentUser, String password) {
175+
return passwordEncoder.matches(password, currentUser.getPassword());
176+
}
177+
178+
/**
179+
* Updates the password of the current logged in user
180+
*/
181+
public Optional<User> updatePassword(CustomUserDetails customUserDetails,
182+
UpdatePasswordRequest updatePasswordRequest) {
183+
User currentUser = userService.getLoggedInUser(customUserDetails.getEmail());
184+
185+
if (!currentPasswordMatches(currentUser, updatePasswordRequest.getOldPassword())) {
186+
logger.info("Current password is invalid for [" + currentUser.getPassword() + "]");
187+
throw new UpdatePasswordException(currentUser.getEmail(), "Invalid current password");
188+
}
189+
String newPassword = passwordEncoder.encode(updatePasswordRequest.getNewPassword());
190+
currentUser.setPassword(newPassword);
191+
userService.save(currentUser);
192+
return Optional.ofNullable(currentUser);
193+
}
167194
}

src/main/java/com/accolite/pru/health/AuthApp/service/MailService.java

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,26 @@ public void sendEmailVerification(String emailVerificationUrl, String to) throws
4747
send(mail);
4848
}
4949

50+
/**
51+
* Send an email to the user indicating an account change event with the correct status
52+
*/
53+
public void sendAccountChangeEmail(String action, String actionStatus, String to) throws IOException,
54+
TemplateException, MessagingException {
55+
Mail mail = new Mail();
56+
mail.setSubject("Account Status Change [Team CEP]");
57+
mail.setTo(to);
58+
mail.setFrom(mailFrom);
59+
mail.getModel().put("userName", to);
60+
mail.getModel().put("action", action);
61+
mail.getModel().put("actionStatus", actionStatus);
62+
63+
templateConfiguration.setClassForTemplateLoading(getClass(), "/templates/");
64+
Template t = templateConfiguration.getTemplate("account-activity-change.ftl");
65+
String mailContent = FreeMarkerTemplateUtils.processTemplateIntoString(t, mail.getModel());
66+
mail.setContent(mailContent);
67+
send(mail);
68+
}
69+
5070
/**
5171
* Sends a simple mail as a MIME Multipart message
5272
*/

src/main/java/com/accolite/pru/health/AuthApp/service/UserService.java

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import com.accolite.pru.health.AuthApp.model.User;
44
import com.accolite.pru.health.AuthApp.repository.UserRepository;
5+
import org.apache.log4j.Logger;
56
import org.springframework.beans.factory.annotation.Autowired;
67
import org.springframework.stereotype.Service;
78

@@ -13,6 +14,8 @@ public class UserService {
1314
@Autowired
1415
private UserRepository userRepository;
1516

17+
private static final Logger logger = Logger.getLogger(UserService.class);
18+
1619
/**
1720
* Finds a user in the database by username
1821
*/
@@ -23,8 +26,16 @@ public Optional<User> findByUsername(String username) {
2326
/**
2427
* Finds a user in the database by email
2528
*/
26-
public Optional<User> findByEmail(String username) {
27-
return userRepository.findByEmail(username);
29+
public Optional<User> findByEmail(String email) {
30+
return userRepository.findByEmail(email);
31+
}
32+
33+
/**
34+
* Finds current logged in user in the database by email
35+
* Note: This will always return a concrete valid instance.
36+
*/
37+
public User getLoggedInUser(String email) {
38+
return findByEmail(email).get();
2839
}
2940

3041
/**

0 commit comments

Comments
 (0)