Commit 0940539f authored by Matija Obreza's avatar Matija Obreza
Browse files

Fixed “Password reset” functionality

parent 730159b4
...@@ -23,4 +23,6 @@ public interface VerificationTokenRepository extends JpaRepository<VerificationT ...@@ -23,4 +23,6 @@ public interface VerificationTokenRepository extends JpaRepository<VerificationT
VerificationToken findByPurposeAndUuid(String string, String tokenUuid); VerificationToken findByPurposeAndUuid(String string, String tokenUuid);
VerificationToken findByUuid(String tokenUuid);
} }
...@@ -20,9 +20,14 @@ import org.genesys2.server.model.impl.User; ...@@ -20,9 +20,14 @@ import org.genesys2.server.model.impl.User;
public interface EMailVerificationService { public interface EMailVerificationService {
void sendVerificationEmail(User user, boolean isVerification); void sendVerificationEmail(User user);
boolean validateEMail(String tokenUuid, String key); void sendPasswordResetEmail(User user);
void cancelValidation(String tokenUuid); void cancelValidation(String tokenUuid);
boolean validateEMail(String tokenUuid, String key);
boolean changePassword(String tokenUuid, String key, String password);
} }
...@@ -16,6 +16,9 @@ ...@@ -16,6 +16,9 @@
package org.genesys2.server.service.impl; package org.genesys2.server.service.impl;
import java.text.MessageFormat;
import java.util.Locale;
import org.apache.commons.lang.RandomStringUtils; import org.apache.commons.lang.RandomStringUtils;
import org.apache.commons.logging.Log; import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory; import org.apache.commons.logging.LogFactory;
...@@ -33,9 +36,6 @@ import org.springframework.beans.factory.annotation.Value; ...@@ -33,9 +36,6 @@ import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
import java.text.MessageFormat;
import java.util.Locale;
@Service @Service
@Transactional(readOnly = true) @Transactional(readOnly = true)
public class EMailVerificationServiceImpl implements EMailVerificationService { public class EMailVerificationServiceImpl implements EMailVerificationService {
...@@ -57,17 +57,25 @@ public class EMailVerificationServiceImpl implements EMailVerificationService { ...@@ -57,17 +57,25 @@ public class EMailVerificationServiceImpl implements EMailVerificationService {
@Value("${base.url}") @Value("${base.url}")
private String baseUrl; private String baseUrl;
@Override
@Transactional @Transactional
public void sendVerificationEmail(User user, boolean isVerification) { public void sendVerificationEmail(User user) {
// Generate new token // Generate new token
VerificationToken verificationToken = generateToken("email-verification", user.getUuid()); VerificationToken verificationToken = generateToken("email-verification", user.getUuid());
Article article=null; Article article = contentService.getGlobalArticle("smtp.email-verification", Locale.ENGLISH);
if (isVerification){ String mailSubject = article.getTitle();
article = contentService.getGlobalArticle("smtp.email-verification", Locale.ENGLISH); String mailBody = MessageFormat.format(article.getBody(), baseUrl, verificationToken.getUuid(), user.getEmail(), verificationToken.getKey());
}else {
article = contentService.getGlobalArticle("smtp.email-password", Locale.ENGLISH); emailService.sendMail(user.getEmail(), user.getName(), mailSubject, mailBody);
} }
@Override
@Transactional
public void sendPasswordResetEmail(User user) {
// Generate new token
VerificationToken verificationToken = generateToken("email-password", user.getUuid());
Article article = contentService.getGlobalArticle("smtp.email-password", Locale.ENGLISH);
String mailSubject = article.getTitle(); String mailSubject = article.getTitle();
String mailBody = MessageFormat.format(article.getBody(), baseUrl, verificationToken.getUuid(), user.getEmail(), verificationToken.getKey()); String mailBody = MessageFormat.format(article.getBody(), baseUrl, verificationToken.getUuid(), user.getEmail(), verificationToken.getKey());
...@@ -88,7 +96,7 @@ public class EMailVerificationServiceImpl implements EMailVerificationService { ...@@ -88,7 +96,7 @@ public class EMailVerificationServiceImpl implements EMailVerificationService {
@Override @Override
@Transactional @Transactional
public void cancelValidation(String tokenUuid) { public void cancelValidation(String tokenUuid) {
VerificationToken verificationToken = verificationTokenRepository.findByPurposeAndUuid("email-verification", tokenUuid); VerificationToken verificationToken = verificationTokenRepository.findByUuid(tokenUuid);
if (verificationToken == null) { if (verificationToken == null) {
LOG.warn("Canceling verification token failed. No such verification token " + tokenUuid); LOG.warn("Canceling verification token failed. No such verification token " + tokenUuid);
} else { } else {
...@@ -108,6 +116,7 @@ public class EMailVerificationServiceImpl implements EMailVerificationService { ...@@ -108,6 +116,7 @@ public class EMailVerificationServiceImpl implements EMailVerificationService {
if (!verificationToken.getKey().equals(key)) { if (!verificationToken.getKey().equals(key)) {
LOG.error("Email verification key invalid for token=" + verificationToken.getUuid() + " providedKey=" + key); LOG.error("Email verification key invalid for token=" + verificationToken.getUuid() + " providedKey=" + key);
return false;
} }
try { try {
...@@ -124,4 +133,33 @@ public class EMailVerificationServiceImpl implements EMailVerificationService { ...@@ -124,4 +133,33 @@ public class EMailVerificationServiceImpl implements EMailVerificationService {
return false; return false;
} }
@Override
@Transactional
public boolean changePassword(String tokenUuid, String key, String password) {
VerificationToken verificationToken = verificationTokenRepository.findByPurposeAndUuid("email-password", tokenUuid);
if (verificationToken == null) {
LOG.warn("No such verification token " + tokenUuid + " key=" + key);
return false;
}
if (!verificationToken.getKey().equals(key)) {
LOG.error("Password reset verification key invalid for token=" + verificationToken.getUuid() + " providedKey=" + key);
return false;
}
try {
User user = userService.getUserByUuid(verificationToken.getData());
userService.updatePassword(user.getId(), password);
// Remove token
verificationTokenRepository.delete(verificationToken);
return true;
} catch (UserException e) {
LOG.error(e.getMessage(), e);
}
return false;
}
} }
...@@ -35,7 +35,7 @@ public class ArticleController extends BaseController { ...@@ -35,7 +35,7 @@ public class ArticleController extends BaseController {
@Autowired @Autowired
private ContentService contentService; private ContentService contentService;
@RequestMapping("{url}") @RequestMapping("{url:.+}")
public String view(ModelMap model, @PathVariable(value = "url") String slug) { public String view(ModelMap model, @PathVariable(value = "url") String slug) {
_logger.debug("Viewing article " + slug); _logger.debug("Viewing article " + slug);
......
...@@ -116,7 +116,7 @@ public class HtmlController extends BaseController { ...@@ -116,7 +116,7 @@ public class HtmlController extends BaseController {
if (!userService.exists(user.getEmail())) { if (!userService.exists(user.getEmail())) {
User newUser = userService.createAccount(user.getEmail(), user.getPassword(), user.getName()); User newUser = userService.createAccount(user.getEmail(), user.getPassword(), user.getName());
emailVerificationService.sendVerificationEmail(newUser, true); emailVerificationService.sendVerificationEmail(newUser);
return "redirect:/content/account-created"; return "redirect:/content/account-created";
} else { } else {
...@@ -134,7 +134,8 @@ public class HtmlController extends BaseController { ...@@ -134,7 +134,8 @@ public class HtmlController extends BaseController {
} }
@RequestMapping(value = "/forgot-password") @RequestMapping(value = "/forgot-password")
public String forgotPassword() { public String forgotPassword(ModelMap model) {
model.addAttribute("blurp", contentService.getGlobalArticle("user.reset-password-instructions", getLocale()));
return "/user/email"; return "/user/email";
} }
......
...@@ -107,7 +107,7 @@ public class UserProfileController extends BaseController { ...@@ -107,7 +107,7 @@ public class UserProfileController extends BaseController {
public String sendEmail(ModelMap model, @PathVariable("uuid") String uuid) { public String sendEmail(ModelMap model, @PathVariable("uuid") String uuid) {
User user = userService.getUserByUuid(uuid); User user = userService.getUserByUuid(uuid);
emailVerificationService.sendVerificationEmail(user,true); emailVerificationService.sendVerificationEmail(user);
return "redirect:/profile/" + user.getUuid(); return "redirect:/profile/" + user.getUuid();
} }
...@@ -117,36 +117,18 @@ public class UserProfileController extends BaseController { ...@@ -117,36 +117,18 @@ public class UserProfileController extends BaseController {
emailVerificationService.cancelValidation(tokenUuid); emailVerificationService.cancelValidation(tokenUuid);
return "redirect:/"; return "redirect:/";
} }
@RequestMapping(value = "/{tokenUuid:.+}/validate", method = RequestMethod.GET)
public String validateEmail(ModelMap model,
@PathVariable("tokenUuid") String tokenUuid,
@RequestParam(value = "email",required = false)String email) {
if (email!=null){
User user=userService.getUserByEmail(email);
model.addAttribute("uuid",user.getUuid());
model.addAttribute("isReset",true);
}
@RequestMapping(value = "/{tokenUuid:.+}/validate", method = RequestMethod.GET)
public String validateEmail(ModelMap model, @PathVariable("tokenUuid") String tokenUuid) {
model.addAttribute("tokenUuid", tokenUuid); model.addAttribute("tokenUuid", tokenUuid);
return "/user/validateemail"; return "/user/validateemail";
} }
@RequestMapping(value = "/{tokenUuid:.+}/validate", method = RequestMethod.POST) @RequestMapping(value = "/{tokenUuid:.+}/validate", method = RequestMethod.POST)
public String validateEmail2(ModelMap model, public String validateEmail2(ModelMap model, @PathVariable("tokenUuid") String tokenUuid, @RequestParam(value = "key", required = true) String key) {
@PathVariable("tokenUuid") String tokenUuid,
@RequestParam(value = "key",required = true) String key,
@RequestParam(value = "uuid",required = false)String uuid,
@RequestParam(value = "isReset",required = false,defaultValue ="0")boolean isReset) {
if (emailVerificationService.validateEMail(tokenUuid, key)) { if (emailVerificationService.validateEMail(tokenUuid, key)) {
// Valid
if (isReset){
model.addAttribute("uuid",uuid);
return "/user/password";
}
return "redirect:/profile"; return "redirect:/profile";
} else { } else {
// Not valid // Not valid
model.addAttribute("tokenUuid", tokenUuid); model.addAttribute("tokenUuid", tokenUuid);
...@@ -155,26 +137,37 @@ public class UserProfileController extends BaseController { ...@@ -155,26 +137,37 @@ public class UserProfileController extends BaseController {
} }
} }
@RequestMapping(value = "/password/reset",method = RequestMethod.POST) @RequestMapping(value = "/password/reset", method = RequestMethod.POST)
public String resetPassword(@RequestParam("email") String email){ public String resetPassword(ModelMap model, @RequestParam("email") String email) {
User user = userService.getUserByEmail(email); User user = userService.getUserByEmail(email);
if(user!=null){
emailVerificationService.sendVerificationEmail(user,false);
}
return "redirect:/profile"; if (user != null) {
} emailVerificationService.sendPasswordResetEmail(user);
}
@RequestMapping(value = "/{uuid}/password/update",method = RequestMethod.POST) return "redirect:/content/user.password-reset-email-sent";
public String updatePassword(@PathVariable("uuid")String uuid, }
@RequestParam("password")String password) throws UserException {
User user=userService.getUserByUuid(uuid);
userService.updatePassword(user.getId(),password); @RequestMapping(value = "/{tokenUuid:.+}/pwdreset", method = RequestMethod.GET)
public String passwordReset(ModelMap model, @PathVariable("tokenUuid") String tokenUuid) {
model.addAttribute("tokenUuid", tokenUuid);
return "/user/password";
}
return "redirect:/profile"; @RequestMapping(value = "/{tokenUuid:.+}/pwdreset", method = RequestMethod.POST)
} public String updatePassword(ModelMap model, @PathVariable("tokenUuid") String tokenUuid, @RequestParam(value = "key", required = true) String key,
@RequestParam("password") String password) throws UserException {
if (emailVerificationService.changePassword(tokenUuid, key, password)) {
return "redirect:/content/user.password-reset";
} else {
// Not valid
model.addAttribute("tokenUuid", tokenUuid);
model.addAttribute("error", "error");
return "/user/password";
}
}
@RequestMapping(value = "/{uuid:.+}/update", method = { RequestMethod.POST }) @RequestMapping(value = "/{uuid:.+}/update", method = { RequestMethod.POST })
@PreAuthorize("hasRole('ADMINISTRATOR') || principal.user.uuid == #uuid") @PreAuthorize("hasRole('ADMINISTRATOR') || principal.user.uuid == #uuid")
......
...@@ -35,6 +35,7 @@ login.remember-me=Remember me ...@@ -35,6 +35,7 @@ login.remember-me=Remember me
login.login-button=Login login.login-button=Login
login.register-now=Create an account login.register-now=Create an account
logout=Logout logout=Logout
login.forgot-password=Forgot password
# Registration # Registration
registration.page.title=Create a user account registration.page.title=Create a user account
...@@ -424,3 +425,6 @@ userprofile.password=Reset password ...@@ -424,3 +425,6 @@ userprofile.password=Reset password
userprofile.enter.email=Enter your email userprofile.enter.email=Enter your email
userprofile.enter.password=Enter new password userprofile.enter.password=Enter new password
userprofile.email.send=Send email userprofile.email.send=Send email
verification.invalid-key=Token key is not valid.
verification.token-key=Validation key
{ {
"en": { "en": {
"title": "Reset password", "title": "Reset password",
"body": "<h2><small>Genesys account</small><br/>Reset password</h2><p><a href=\"{0}/profile/{1}/validate?email={2}\">Recovery </a></p><h2>Validation key: {3}</h2><p>If you didn't make this request, <a href=\"{0}/profile/{1}/cancel\">click here to cancel</a>.</p><p>Thanks,<br/ >Genesys team</p>" "body": "<h2><small>Genesys account</small><br/>Reset password</h2><p><a href=\"{0}/profile/{1}/pwdreset\">Recovery </a></p><h2>Validation key: {3}</h2><p>If you didn't make this request, <a href=\"{0}/profile/{1}/cancel\">click here to cancel</a>.</p><p>Thanks,<br/ >Genesys team</p>"
} }
} }
\ No newline at end of file
{
"en": {
"title": "Password changed",
"body": "<p>Your password is now updated. You may now use it to <a href=\"/profile\">log in</a>.</p>"
}
}
\ No newline at end of file
{
"en": {
"title": "Password reset",
"body": "<p>We've sent an email with further instructions to the specified email address. The email should arrive shortly.</p><p>Please check the <b>SPAM</b> or <b>Junk mail</b> folders!</p>"
}
}
\ No newline at end of file
{
"en": {
"title": "Resetting your password",
"body": "<p>To reset your password, you have to provide your registered email address.</p><p>In the unlikely case that the email address does not exist in our system, we will silently ignore your password reset request and pretend all was okay with your input.</p>"
}
}
\ No newline at end of file
...@@ -54,7 +54,6 @@ ...@@ -54,7 +54,6 @@
<prop key="hibernate.hbm2ddl.auto">${db.hbm2ddl}</prop> <prop key="hibernate.hbm2ddl.auto">${db.hbm2ddl}</prop>
<prop key="hibernate.search.default.indexBase">${lucene.indexDir}</prop> <prop key="hibernate.search.default.indexBase">${lucene.indexDir}</prop>
<prop key="hibernate.search.default.exclusive_index_use">false</prop> <prop key="hibernate.search.default.exclusive_index_use">false</prop>
<prop key="hibernate.dialect">org.hibernate.dialect.MySQLDialect</prop>
</props> </props>
</property> </property>
<property name="packagesToScan"> <property name="packagesToScan">
......
...@@ -35,15 +35,10 @@ ...@@ -35,15 +35,10 @@
</div> </div>
</div> </div>
<div class="form-group"> <div class="form-group">
<div class="col-lg-offset-2 col-lg-3"> <div class="col-lg-offset-2 col-lg-10">
<a href="/forgot-password" id="forgot-password" >Forgot password</a>
</div>
</div>
<div class="form-group">
<div class="col-lg-offset-2 col-lg-3">
<input type="submit" value="<spring:message code="login.login-button" />" class="btn btn-primary" /> <input type="submit" value="<spring:message code="login.login-button" />" class="btn btn-primary" />
<a href="registration" id="registration" class="btn btn-default"><spring:message code="login.register-now"/></a> <a href="registration" id="registration" class="btn btn-default"><spring:message code="login.register-now"/></a>
<a href="forgot-password" id="forgot-password" class="btn"><spring:message code="login.forgot-password"/></a>
</div> </div>
</div> </div>
</form> </form>
......
...@@ -10,15 +10,18 @@ ...@@ -10,15 +10,18 @@
<h1> <h1>
<spring:message code="userprofile.password" /> <spring:message code="userprofile.password" />
</h1> </h1>
<form class="form-horizontal" action="<c:url value="/profile/password/reset"/>" method="post">
<div class="form-group"> <%@include file="/WEB-INF/jsp/content/include/blurp-display.jsp"%>
<label for="email" class="col-lg-2 control-label"><spring:message code="userprofile.enter.email" /></label>
<div class="col-lg-3"><input type="text" id="email" name="email" class="span3 form-control" /></div> <form class="form-horizontal" action="<c:url value="/profile/password/reset"/>" method="post">
<div class="col-lg-1"> <div class="form-group">
<input type="submit" value="<spring:message code="userprofile.email.send" />" class="btn btn-primary" /> <label for="email" class="col-lg-2 control-label"><spring:message code="userprofile.enter.email" /></label>
</div> <div class="col-lg-3"><input type="text" id="email" name="email" class="span3 form-control" /></div>
<div class="col-lg-1">
<input type="submit" value="<spring:message code="userprofile.email.send" />" class="btn btn-primary" />
</div> </div>
</form> </div>
</form>
</body> </body>
</html> </html>
\ No newline at end of file
...@@ -10,14 +10,24 @@ ...@@ -10,14 +10,24 @@
<h1> <h1>
<spring:message code="userprofile.password" /> <spring:message code="userprofile.password" />
</h1> </h1>
<form class="form-horizontal" action="<c:url value="/profile/${uuid}/password/update"/>" method="post"> <c:if test="${error ne null}">
<div class="form-group"> <div class="alert alert-danger"><spring:message code="verification.invalid-key"/></div>
<label for="password" class="col-lg-2 control-label"><spring:message code="userprofile.enter.password" /></label> </c:if>
<div class="col-lg-3"><input type="password" id="password" name="password" class="span3 form-control" /></div>
<div class="col-lg-1"> <form class="form-horizontal" action="<c:url value="/profile/${tokenUuid}/pwdreset"/>" method="post">
<input type="submit" value="<spring:message code="userprofile.password" />" class="btn btn-primary" /> <div class="form-group">
</div> <label for="password" class="col-lg-2 control-label"><spring:message code="verification.token-key" /></label>
<div class="col-lg-3"><input type="text" id="key" name="key" class="span1 form-control" maxlength="4" placeholder="..." value="" /></div>
</div>
<div class="form-group">
<label for="password" class="col-lg-2 control-label"><spring:message code="userprofile.enter.password" /></label>
<div class="col-lg-3"><input type="password" id="password" name="password" class="span3 form-control" value="" /></div>
</div>
<div class="form-group">
<div class="col-lg-1">
<input type="submit" value="<spring:message code="userprofile.password" />" class="btn btn-primary" />
</div> </div>
</form> </div>
</form>
</body> </body>
</html> </html>
\ No newline at end of file
...@@ -12,8 +12,7 @@ ...@@ -12,8 +12,7 @@
</h1> </h1>
<form role="form" class="form-vertical validate" action="<c:url value="/profile/${tokenUuid}/validate" />" method="post"> <form role="form" class="form-vertical validate" action="<c:url value="/profile/${tokenUuid}/validate" />" method="post">
<input type="hidden" value="${isReset}" name="isReset">
<input type="hidden" value="${uuid}" name="uuid">
<div class="col-lg-3"> <div class="col-lg-3">
<input type="text" id="key" name="key" class="span1 form-control" maxlength="4"/> <input type="text" id="key" name="key" class="span1 form-control" maxlength="4"/>
</div> </div>
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment