Skip to content

Commit

Permalink
Add email confirmation for password change (#61)
Browse files Browse the repository at this point in the history
* Add email confirmation for password change

Token:
-Added data field to store data like password or email
-Added @Enumerated annotataion to TokenType
TokenType:
-Added CONFIRMATION value
UserService:
-Added rootUrl parametr to changeUserPassword method
-Added confirmChangeUserPassword method
UserServiceImpl:
-Modified changeUserPassword method to sending emails
-Added confirmChangeUserPassword method
UserController:
-Modified changeUserPassword method
-Added confirmChangeUserPassword method

* Update TokenType value

-Changed enum value from CONFIRMATION to CHANGE_EMAIL_CONFIRMATION

* Change TokenType values

-Changed from REGISTER to REGISTER_CONFIRMATION
-Remove PASSWORD and EMAIL values

* Change password finals to be more precise

* Change TokenType value

-Changed from CHANGE_EMAIL_CONFIRMATION to CHANGE_PASSWORD_CONFIRMATION

Co-authored-by: Dawid Bielecki <dawciobiel@gmail.com>
  • Loading branch information
DawidStuzynski and dawciobiel committed Aug 30, 2022
1 parent 870b94c commit c2ca351
Show file tree
Hide file tree
Showing 6 changed files with 59 additions and 11 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,13 @@ ResponseEntity<String> confirmRegistration(@RequestParam String tokenValue) {
}

@PostMapping("/change-password")
ResponseEntity<String> changeUserPassword(@RequestBody ChangeUserPasswordDTO dto, Principal principal) {

return new ResponseEntity<>(userService.changeUserPassword(dto, principal.getName()), HttpStatus.OK);
ResponseEntity<String> changeUserPassword(@RequestBody ChangeUserPasswordDTO dto, Principal principal, HttpServletRequest request) {
return new ResponseEntity<>(userService.changeUserPassword(dto, principal.getName(), request.getRequestURL().toString()), HttpStatus.OK);
}

@GetMapping("/confirm-change-password")
ResponseEntity<String> confirmChangeUserPassword(@RequestParam String tokenValue) {
return new ResponseEntity<>(userService.confirmChangeUserPassword(tokenValue), HttpStatus.OK);
}

@PatchMapping
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ public class Token {

private String value = UUID.randomUUID().toString();

private String data;

@ManyToOne(cascade = {CascadeType.PERSIST, CascadeType.MERGE})
private User user;

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
package pl.simpleascoding.tutoringplatform.domain.token;

public enum TokenType {
REGISTER, PASSWORD, EMAIL
REGISTER_CONFIRMATION, CHANGE_PASSWORD_CONFIRMATION
}
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ public void cleanExpiredTokens() {
List<Token> expiredTokens = tokenRepository.findTokensByExpiresAtBefore(LocalDateTime.now());
List<User> inactiveUsers = expiredTokens
.stream()
.filter(token -> TokenType.REGISTER.equals(token.getType()) && Optional.ofNullable(token.getConfirmedAt()).isEmpty())
.filter(token -> TokenType.REGISTER_CONFIRMATION.equals(token.getType()) && Optional.ofNullable(token.getConfirmedAt()).isEmpty())
.map(Token::getUser)
.toList();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,9 @@ public interface UserService extends UserDetailsService {

String confirmUserRegistration(String token);

String changeUserPassword(ChangeUserPasswordDTO dto, String username);
String changeUserPassword(ChangeUserPasswordDTO dto, String username, String rootUrl);

String confirmChangeUserPassword(String token);

RscpDTO<UserDTO> modifyUser(ModifyUserDTO dto, String username);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
@RequiredArgsConstructor
class UserServiceImpl implements UserService {


private final UserRepository userRepository;

private final TokenRepository tokenRepository;
Expand All @@ -34,11 +35,19 @@ class UserServiceImpl implements UserService {

private final UserModelMapper userModelMapper;


private static final String REGISTRATION_MAIL_SUBJECT = "Confirm your email";
private static final String REGISTRATION_MAIL_TEXT = "Hi %s, please visit the link below to confirm your email address and activate your account: \n%s";
private static final String REGISTRATION_LINK = "%s/confirm-registration?tokenValue=%s";

private static final String USER_NOT_FOUND_MSG = "User with \"%s\" username, has not been found";
private static final String PASSWORD_CHANGE_CONFIRMATION_MAIL_SUBJECT = "Confirm your password change";
private static final String PASSWORD_CHANGE_CONFIRMATION_MAIL_TEXT = "Hi %s, please visit the link below to confirm your password change: \n%s";
private static final String PASSWORD_CHANGE_CONFIRMATION_LINK = "%s/confirm-change-password?tokenValue=%s";

private final UserRepository userRepository;
private final TokenRepository tokenRepository;
private final PasswordEncoder passwordEncoder;
private final JavaMailSender mailSender;

@Override
public User getUserById(long id) {
Expand All @@ -65,7 +74,7 @@ public String createUser(CreateUserDTO dto, String rootUrl) {
user.setPassword(passwordEncoder.encode(user.getPassword()));
user.getRoles().add(RoleType.USER);

Token token = new Token(TokenType.REGISTER);
Token token = new Token(TokenType.REGISTER_CONFIRMATION);
user.getTokens().add(token);

userRepository.save(user);
Expand All @@ -85,7 +94,7 @@ public String createUser(CreateUserDTO dto, String rootUrl) {
public String confirmUserRegistration(String tokenValue) {
Token token = tokenRepository.findTokenByValue(tokenValue).orElseThrow(TokenNotFoundException::new);

if (token.getType() != TokenType.REGISTER) {
if (token.getType() != TokenType.REGISTER_CONFIRMATION) {
throw new InvalidTokenException();
}

Expand All @@ -112,14 +121,26 @@ public String confirmUserRegistration(String tokenValue) {
* @param username Username of the user
* @return The status of the operation
*/

@Override
@Transactional
public String changeUserPassword(ChangeUserPasswordDTO newPasswordsData, String username) {
public String changeUserPassword(ChangeUserPasswordDTO newPasswordsData, String username, String rootUrl) {
User userEntity = userRepository.findUserByUsername(username)
.orElseThrow(() -> new UsernameNotFoundException(username));

if (isChangeAllowed(userEntity.getPassword(), newPasswordsData)) {
userEntity.setPassword(passwordEncoder.encode(newPasswordsData.newPassword()));

Token token = new Token(TokenType.CHANGE_PASSWORD_CONFIRMATION);
String password = newPasswordsData.newPassword();
token.setData(passwordEncoder.encode(password));
userEntity.getTokens().add(token);

SimpleMailMessage message = new SimpleMailMessage();
message.setTo(userEntity.getEmail());
message.setSubject(PASSWORD_CHANGE_CONFIRMATION_MAIL_SUBJECT);
message.setText(String.format(PASSWORD_CHANGE_CONFIRMATION_MAIL_TEXT, userEntity.getName(), String.format(PASSWORD_CHANGE_CONFIRMATION_LINK, rootUrl, token.getValue())));

mailSender.send(message);

return HttpStatus.OK.getReasonPhrase();
} else {
Expand All @@ -129,14 +150,34 @@ public String changeUserPassword(ChangeUserPasswordDTO newPasswordsData, String

@Override
@Transactional
public String confirmChangeUserPassword(String tokenValue) {
Token token = tokenRepository.findTokenByValue(tokenValue).orElseThrow(TokenNotFoundException::new);

if (token.getType() != TokenType.CHANGE_PASSWORD_CONFIRMATION) {
throw new InvalidTokenException();
}

if (token.getConfirmedAt() != null) {
throw new TokenAlreadyConfirmedException();
}
String password = token.getData();
token.getUser().setPassword(password);

token.confirm();

return HttpStatus.OK.getReasonPhrase();
}

public RscpDTO<UserDTO> modifyUser(ModifyUserDTO dto, String username) {
User userEntity = getUserByUsername(username);

if(dto.name() != null && !dto.name().isEmpty()){
userEntity.setName(dto.name());
}
if(dto.surname() != null && !dto.surname().isEmpty()){
userEntity.setSurname(dto.surname());
}

return new RscpDTO<>(RscpStatus.OK, "User modification completed successfully.", userModelMapper.mapUserEntityToUserDTO(userEntity));
}

Expand Down

0 comments on commit c2ca351

Please sign in to comment.