89 password reset

This commit is contained in:
Artem.Arefev 2019-05-19 00:42:24 +04:00
parent 56f083e757
commit 90799356ab
13 changed files with 162 additions and 77 deletions

View File

@ -21,4 +21,5 @@ public class Constants {
public static final String PASSWORD_RESET_REQUEST_PAGE = "/resetRequest"; public static final String PASSWORD_RESET_REQUEST_PAGE = "/resetRequest";
public static final String PASSWORD_RESET_PAGE = "/reset"; public static final String PASSWORD_RESET_PAGE = "/reset";
public static final int RESET_KEY_LENGTH = 6;
} }

View File

@ -104,7 +104,8 @@ public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
.antMatchers("/css/**") .antMatchers("/css/**")
.antMatchers("/js/**") .antMatchers("/js/**")
.antMatchers("/templates/**") .antMatchers("/templates/**")
.antMatchers("/webjars/**"); .antMatchers("/webjars/**")
.antMatchers("/img/**");
} }
@Autowired @Autowired

View File

@ -34,11 +34,11 @@ public class AdviceController {
public AdviceController(UserService userService) { public AdviceController(UserService userService) {
this.userService = userService; this.userService = userService;
} }
//
@ModelAttribute("currentUser") // @ModelAttribute("currentUser")
public String getCurrentUser() { // public String getCurrentUser() {
return userService.getCurrentUser().getUserAbbreviate(); // return userService.getCurrentUser().getUserAbbreviate();
} // }
@ModelAttribute("flashMessage") @ModelAttribute("flashMessage")
public String getFlashMessage() { public String getFlashMessage() {

View File

@ -9,9 +9,9 @@ public enum ErrorConstants {
USER_EMAIL_EXISTS(102, "Пользователь с таким почтовым ящиком уже существует"), USER_EMAIL_EXISTS(102, "Пользователь с таким почтовым ящиком уже существует"),
USER_LOGIN_EXISTS(103, "Пользователь с таким логином уже существует"), USER_LOGIN_EXISTS(103, "Пользователь с таким логином уже существует"),
USER_PASSWORDS_NOT_VALID_OR_NOT_MATCH(104, "Пароли введены неверно"), USER_PASSWORDS_NOT_VALID_OR_NOT_MATCH(104, "Пароли введены неверно"),
USER_NOT_FOUND(105, "User is not found"), USER_NOT_FOUND(105, "Аккаунт не найден"),
USER_NOT_ACTIVATED(106, "User is not activated"), USER_NOT_ACTIVATED(106, "User is not activated"),
USER_RESET_ERROR(107, "Invalid reset key"), USER_RESET_ERROR(107, "Некорректный ключ подтверждения"),
USER_UNDEAD_ERROR(108, "Can't edit/delete that user"), USER_UNDEAD_ERROR(108, "Can't edit/delete that user"),
FILE_UPLOAD_ERROR(110, "File upload error"), FILE_UPLOAD_ERROR(110, "File upload error"),
USER_SENDING_MAIL_EXCEPTION(111, "Во время отправки приглашения пользователю произошла ошибка"); USER_SENDING_MAIL_EXCEPTION(111, "Во время отправки приглашения пользователю произошла ошибка");

View File

@ -148,16 +148,15 @@ public class UserController extends OdinController<UserListDto, UserDto> {
} }
@PostMapping(PASSWORD_RESET_REQUEST_URL) @PostMapping(PASSWORD_RESET_REQUEST_URL)
public Response<Boolean> requestPasswordReset(@RequestParam("email") String email) { public void requestPasswordReset(@RequestParam("email") String email) {
log.debug("REST: UserController.requestPasswordReset( {} )", email); log.debug("REST: UserController.requestPasswordReset( {} )", email);
return new Response<>(userService.requestUserPasswordReset(email)); userService.requestUserPasswordReset(email);
} }
@PostMapping(PASSWORD_RESET_URL) @PostMapping(PASSWORD_RESET_URL)
public Response<Boolean> finishPasswordReset(@RequestParam("key") String key, public Response<Boolean> finishPasswordReset(@RequestBody UserResetPasswordDto userResetPasswordDto) {
@RequestBody UserResetPasswordDto userResetPasswordDto) { log.debug("REST: UserController.requestPasswordReset( {} )", userResetPasswordDto.getResetKey());
log.debug("REST: UserController.requestPasswordReset( {} )", key); return new Response<>(userService.completeUserPasswordReset(userResetPasswordDto));
return new Response<>(userService.completeUserPasswordReset(key, userResetPasswordDto));
} }
@PostMapping("/changePassword") @PostMapping("/changePassword")

View File

@ -14,6 +14,10 @@ public class UserResetPasswordDto {
@Size(min = Constants.MIN_PASSWORD_LENGTH, max = 50) @Size(min = Constants.MIN_PASSWORD_LENGTH, max = 50)
private String passwordConfirm; private String passwordConfirm;
@NotEmpty
@Size(min = Constants.RESET_KEY_LENGTH)
private String resetKey;
public String getPassword() { public String getPassword() {
return password; return password;
} }
@ -25,4 +29,8 @@ public class UserResetPasswordDto {
public boolean isPasswordsValid() { public boolean isPasswordsValid() {
return Objects.equals(password, passwordConfirm); return Objects.equals(password, passwordConfirm);
} }
public String getResetKey() {
return resetKey;
}
} }

View File

@ -106,8 +106,7 @@ public class MailService {
sendEmailFromTemplate(user, "activationEmail", Constants.MAIL_ACTIVATE); sendEmailFromTemplate(user, "activationEmail", Constants.MAIL_ACTIVATE);
} }
@Async public void sendPasswordResetMail(User user) throws MessagingException, MailException {
public void sendPasswordResetMail(User user) {
sendEmailFromTemplate(user, "passwordResetEmail", Constants.MAIL_RESET); sendEmailFromTemplate(user, "passwordResetEmail", Constants.MAIL_RESET);
} }
@ -118,6 +117,5 @@ public class MailService {
@Async @Async
public void sendChangePasswordMail(User user) { public void sendChangePasswordMail(User user) {
sendEmailFromTemplate(user, "passwordChangeEmail", Constants.MAIL_CHANGE_PASSWORD); sendEmailFromTemplate(user, "passwordChangeEmail", Constants.MAIL_CHANGE_PASSWORD);
} }
} }

View File

@ -236,7 +236,7 @@ public class UserService implements UserDetailsService {
mailService.sendChangePasswordMail(user); mailService.sendChangePasswordMail(user);
} }
public boolean requestUserPasswordReset(String email) { public boolean requestUserPasswordReset(String email) {
User user = userRepository.findOneByEmailIgnoreCase(email); User user = userRepository.findOneByEmailIgnoreCase(email);
if (user == null) { if (user == null) {
throw new UserNotFoundException(email); throw new UserNotFoundException(email);
@ -247,23 +247,30 @@ public class UserService implements UserDetailsService {
user.setResetKey(UserUtils.generateResetKey()); user.setResetKey(UserUtils.generateResetKey());
user.setResetDate(new Date()); user.setResetDate(new Date());
user = userRepository.save(user); user = userRepository.save(user);
mailService.sendPasswordResetMail(user); try {
mailService.sendPasswordResetMail(user);
} catch (MessagingException | MailException e) {
throw new UserSendingMailException(email);
}
log.debug("Created Reset Password Request for User: {}", user.getLogin()); log.debug("Created Reset Password Request for User: {}", user.getLogin());
return true; return true;
} }
public boolean completeUserPasswordReset(String key, UserResetPasswordDto userResetPasswordDto) { public boolean completeUserPasswordReset(UserResetPasswordDto userResetPasswordDto) {
if (!userResetPasswordDto.isPasswordsValid()) { if (!userResetPasswordDto.isPasswordsValid()) {
throw new UserPasswordsNotValidOrNotMatchException(""); throw new UserPasswordsNotValidOrNotMatchException("Пароли не совпадают");
} }
User user = userRepository.findOneByResetKey(key); User user = userRepository.findOneByResetKey(userResetPasswordDto.getResetKey());
if (user == null) { if (user == null) {
throw new UserResetKeyError(key); throw new UserResetKeyError(userResetPasswordDto.getResetKey());
} }
user.setPassword(passwordEncoder.encode(userResetPasswordDto.getPassword())); user.setPassword(passwordEncoder.encode(userResetPasswordDto.getPassword()));
user.setResetKey(null); user.setResetKey(null);
user.setResetDate(null); user.setResetDate(null);
user = userRepository.save(user); user = userRepository.save(user);
mailService.sendChangePasswordMail(user);
log.debug("Reset Password for User: {}", user.getLogin()); log.debug("Reset Password for User: {}", user.getLogin());
return true; return true;
} }

View File

@ -1,23 +1,19 @@
<!DOCTYPE html> <!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org"> <html xmlns:th="http://www.thymeleaf.org">
<head> <head>
<title>Password reset</title> <title>Восстановление пароля</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
<link rel="shortcut icon" th:href="@{|${baseUrl}/favicon.ico|}"/> <link rel="shortcut icon" th:href="@{|${baseUrl}/favicon.ico|}"/>
</head> </head>
<body> <body>
<p> <p>
Dear <span th:text="${user.firstName + ' ' + user.lastName}">Ivan Ivanov</span> Дорогой <span th:text="${user.firstName + ' ' + user.lastName}">Ivan Ivanov</span>
</p> </p>
<p> <p>
For your account a password reset was requested, please click on the URL below to Ваш ключ для восстановления пароля <span th:text="${user.resetKey}"></span>
</p> </p>
<p> <p>
<a th:href="@{|${baseUrl}/reset?key=${user.resetKey}|}" С уважением,
th:text="@{|${baseUrl}/reset?key=${user.resetKey}|}">Reset Link</a>
</p>
<p>
Regards,
<br/> <br/>
<em>Balance Team.</em> <em>Balance Team.</em>
</p> </p>

View File

@ -0,0 +1,3 @@
.loader {
padding-left:50%
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 673 B

View File

@ -35,10 +35,7 @@ function changePassword() {
function inviteUser() { function inviteUser() {
email = document.getElementById("email").value; email = document.getElementById("email").value;
re = /\S+@\S+\.\S+/; if (!isEmailValid(email)) {
if (!re.test(email)) {
showFeedbackMessage("Некорректный почтовый ящик", MessageTypesEnum.WARNING); showFeedbackMessage("Некорректный почтовый ящик", MessageTypesEnum.WARNING);
return; return;
} }
@ -56,3 +53,76 @@ function inviteUser() {
} }
}) })
} }
function requestResetPassword() {
email = document.getElementById("emailReset").value
if (!isEmailValid(email)) {
showFeedbackMessage("Некорректный почтовый ящик", MessageTypesEnum.WARNING);
return;
}
document.getElementById("dvloader").hidden = false;
$.ajax({
url:"/api/1.0/users/password-reset-request?email=" + email,
contentType: "application/json; charset=utf-8",
method: "POST",
success: function() {
showFeedbackMessage("Проверочный код был отправлен на указанный почтовый ящик", MessageTypesEnum.SUCCESS)
document.getElementById("passwordNew").hidden = false
document.getElementById("passwordConfirm").hidden = false
document.getElementById("btnReset").hidden = false
document.getElementById("resetKey").hidden = false
document.getElementById("emailReset").hidden = true
document.getElementById("btnSend").hidden = true
document.getElementById("dvloader").hidden = true;
},
error: function(errorData) {
showFeedbackMessage(errorData.responseJSON.error.message, MessageTypesEnum.WARNING)
document.getElementById("dvloader").hidden = true;
}
})
}
function resetPassword() {
passwordNew = document.getElementById("passwordNew").value;
passwordConfirm = document.getElementById("passwordConfirm").value;
resetKey = document.getElementById("resetKey").value;
if ([passwordNew, passwordConfirm, resetKey].includes("")) {
showFeedbackMessage("Заполните все поля", MessageTypesEnum.WARNING);
return;
}
if (passwordNew != passwordConfirm) {
showFeedbackMessage("Пароли не совпадают", MessageTypesEnum.WARNING);
return;
}
$.ajax({
url:"/api/1.0/users/password-reset",
contentType: "application/json; charset=utf-8",
method: "POST",
data: JSON.stringify({
"password": passwordNew,
"passwordConfirm": passwordConfirm,
"resetKey": resetKey,
}),
success: function() {
showFeedbackMessage("Пользователь был успешно приглашен", MessageTypesEnum.SUCCESS)
window.location.href = "/login"
},
error: function(errorData) {
showFeedbackMessage(errorData.responseJSON.error.message, MessageTypesEnum.WARNING)
}
})
}
function isEmailValid(email) {
re = /\S+@\S+\.\S+/;
return re.test(email)
}

View File

@ -1,9 +1,10 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en" <html lang="en"
xmlns:th="http://www.thymeleaf.org"
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
layout:decorator="default"> layout:decorator="default" xmlns:th="http://www.w3.org/1999/xhtml">
<head> <head>
<script src="/js/users.js"></script>
<link rel="stylesheet" href="../css/base.css"/>
</head> </head>
<body> <body>
<nav layout:fragment="navbar"> <nav layout:fragment="navbar">
@ -12,46 +13,47 @@
class="fa fa-plane fa-4" aria-hidden="true"></i> Balance</span></a> class="fa fa-plane fa-4" aria-hidden="true"></i> Balance</span></a>
</div> </div>
</nav> </nav>
<div class="container" layout:fragment="content"> <div layout:fragment="content">
<form id="reset-form" method="post" class="margined-top-10"> <section class="bg-light" id="portfolio">
<fieldset>
<div class="form-group">
<input type="email" name="email" id="email" class="form-control"
placeholder="E-Mail" required="true" autofocus="autofocus"/>
</div>
<button type="submit" class="btn btn-success btn-block">Сбросить пароль</button>
<div class="form-group">
<small class="form-text text-muted">
<a href="/login">Вернуться к странице входа</a>
</small>
</div>
</fieldset>
</form>
</div>
<th:block layout:fragment="data-scripts">
<script type="text/javascript">
/*<![CDATA[*/
$(document).ready(function () {
$("#reset-form").submit(function () {
var email = $("#email").val();
if (isEmpty(email)) {
showFeedbackMessage("Адрес электронной почты не задан", MessageTypesEnum.DANGER);
return false;
}
postToRest(urlUsersPasswordResetRequest + "?email=" + email, null,
function () {
showFeedbackMessage("Запрос на смену пароля отправлен");
},
function () {
$("#email").val("");
}
);
return false;
});
});
/*]]>*/
</script> <div class="container">
</th:block> <div class="row justify-content-md-center">
<div class="col-lg-6">
<div class="form-group">
<input type="text" name="email" id="emailReset" class="form-control"
placeholder="E-Mail"/>
</div>
<div class="form-group">
<input type="password" name="email" id="passwordNew" class="form-control"
placeholder="Новый пароль" hidden="true"/>
</div>
<div class="form-group">
<input type="password" name="email" id="passwordConfirm" class="form-control"
placeholder="Подтвердите пароль" hidden="true"/>
</div>
<div class="form-group">
<input type="text" name="email" id="resetKey" class="form-control"
placeholder="Код подтверждения" hidden="true"/>
</div>
<div id="dvloader" class="loader" hidden="true"><img src="../img/main/ajax-loader.gif" /></div>
<button id="btnSend" type="button" onclick="requestResetPassword()"
class="btn btn-success btn-block">
Отправить код подтверждения
</button>
<button id="btnReset" hidden="true" type="button" onclick="resetPassword()"
class="btn btn-success btn-block">
Сбросить
пароль
</button>
<div class="form-group">
<small class="form-text text-muted">
<a href="/login">Вернуться к странице входа</a>
</small>
</div>
</div>
</div>
</div>
</section>
</div>
</body> </body>
</html> </html>