-
Notifications
You must be signed in to change notification settings - Fork 8
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* feat(change-password): added a menu for logged in users to change password * feat(change-password): send mail code * feat(change-password): add new services * feat(change-password): feature implemented * feat(change-password): handle password > 20 characters error and update email message * feat(change-password): put reset code logic in session * feat(change-password): add enforcedWebapRootUrl var * Sorted out a few details on password override functionality --------- Co-authored-by: Dorian Grasset <[email protected]>
- Loading branch information
1 parent
865a0bf
commit 74ab442
Showing
11 changed files
with
345 additions
and
24 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
30 changes: 30 additions & 0 deletions
30
src/main/java/fr/cirad/security/ResetCodeExpirationFilter.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
package fr.cirad.security; | ||
|
||
import javax.servlet.*; | ||
import javax.servlet.http.HttpServletRequest; | ||
import javax.servlet.http.HttpSession; | ||
import java.io.IOException; | ||
import java.time.LocalDateTime; | ||
|
||
public class ResetCodeExpirationFilter implements Filter { | ||
|
||
private static final String RESET_EXPIRATION_KEY = "resetExpiration"; | ||
|
||
@Override | ||
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { | ||
HttpServletRequest httpRequest = (HttpServletRequest) request; | ||
HttpSession session = httpRequest.getSession(false); | ||
|
||
if (session != null) { | ||
LocalDateTime expiration = (LocalDateTime) session.getAttribute(RESET_EXPIRATION_KEY); | ||
if (expiration != null && expiration.isBefore(LocalDateTime.now())) { | ||
// Clear expired reset information | ||
session.removeAttribute("resetCode"); | ||
session.removeAttribute("resetEmail"); | ||
session.removeAttribute(RESET_EXPIRATION_KEY); | ||
} | ||
} | ||
|
||
chain.doFilter(request, response); | ||
} | ||
} |
113 changes: 113 additions & 0 deletions
113
src/main/java/fr/cirad/service/PasswordResetService.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,113 @@ | ||
package fr.cirad.service; | ||
|
||
import fr.cirad.security.ReloadableInMemoryDaoImpl; | ||
import fr.cirad.security.UserWithMethod; | ||
import fr.cirad.tools.AppConfig; | ||
import fr.cirad.web.controller.BackOfficeController; | ||
|
||
import org.apache.log4j.Logger; | ||
import org.springframework.beans.factory.annotation.Autowired; | ||
import org.springframework.mail.SimpleMailMessage; | ||
import org.springframework.mail.javamail.JavaMailSender; | ||
import org.springframework.stereotype.Service; | ||
|
||
import javax.servlet.http.HttpServletRequest; | ||
import javax.servlet.http.HttpSession; | ||
import java.io.IOException; | ||
import java.time.LocalDateTime; | ||
|
||
@Service | ||
public class PasswordResetService { | ||
|
||
private static final Logger LOG = Logger.getLogger(PasswordResetService.class); | ||
|
||
private static final String RESET_CODE_KEY = "resetCode"; | ||
private static final String RESET_EMAIL_KEY = "resetEmail"; | ||
private static final String RESET_EXPIRATION_KEY = "resetExpiration"; | ||
|
||
@Autowired | ||
private JavaMailSender mailSender; | ||
|
||
@Autowired | ||
private AppConfig appConfig; | ||
|
||
@Autowired | ||
private ReloadableInMemoryDaoImpl userDao; | ||
|
||
public String generateResetCode() { | ||
return String.format("%08d", new java.util.Random().nextInt(100000000)); | ||
} | ||
|
||
public boolean sendResetPasswordEmail(String email, HttpSession session, HttpServletRequest request) { | ||
UserWithMethod user = userDao.getUserWithMethodByEmailAddress(email); | ||
if (user == null) { | ||
return true; // We return true to not disclose if the email exists | ||
} | ||
|
||
String resetCode = generateResetCode(); | ||
session.setAttribute(RESET_CODE_KEY, resetCode); | ||
session.setAttribute(RESET_EMAIL_KEY, email); | ||
session.setAttribute(RESET_EXPIRATION_KEY, LocalDateTime.now().plusMinutes(5)); | ||
|
||
try { | ||
SimpleMailMessage message = new SimpleMailMessage(); | ||
|
||
String sWebAppRoot = appConfig.get("enforcedWebapRootUrl"); | ||
String enforcedWebapRootUrl = (sWebAppRoot == null ? BackOfficeController.determinePublicHostName(request) + request.getContextPath() : sWebAppRoot); | ||
if (enforcedWebapRootUrl == null || enforcedWebapRootUrl.trim().isEmpty()) | ||
LOG.warn("enforcedWebapRootUrl is not set in the application.properties file. Using the default value."); | ||
|
||
String subject = "Gigwa - Password reset request"; | ||
String emailContent = "Hello,\n\nYou have requested to reset your password for the following Gigwa instance: " + enforcedWebapRootUrl + "\nYour password reset code is: " + resetCode + "\nPlease enter this code in the application to reset your password. This code will expire in 5 minutes.\n"; | ||
|
||
message.setTo(email); | ||
message.setSubject(subject); | ||
message.setText(emailContent); | ||
|
||
mailSender.send(message); | ||
return true; | ||
} catch (Exception e) { | ||
LOG.error("Unable to send password reset email", e); | ||
return false; | ||
} | ||
} | ||
|
||
public boolean validateResetCode(String code, HttpSession session) { | ||
String storedCode = (String) session.getAttribute(RESET_CODE_KEY); | ||
LocalDateTime expiration = (LocalDateTime) session.getAttribute(RESET_EXPIRATION_KEY); | ||
|
||
if (storedCode == null || !storedCode.equals(code) || expiration == null) { | ||
return false; | ||
} | ||
|
||
return expiration.isAfter(LocalDateTime.now()); | ||
} | ||
|
||
public boolean updatePassword(String code, String newPassword, HttpSession session) { | ||
if (!validateResetCode(code, session)) { | ||
return false; | ||
} | ||
|
||
String email = (String) session.getAttribute(RESET_EMAIL_KEY); | ||
UserWithMethod user = userDao.getUserWithMethodByEmailAddress(email); | ||
if (user == null) { | ||
return false; | ||
} | ||
|
||
try { | ||
userDao.saveOrUpdateUser(user.getUsername(), newPassword, | ||
user.getAuthorities(), | ||
user.isEnabled(), user.getMethod(), user.getEmail()); | ||
} catch (IOException e) { | ||
e.printStackTrace(); | ||
return false; | ||
} | ||
|
||
// Clear the reset information from the session | ||
session.removeAttribute(RESET_CODE_KEY); | ||
session.removeAttribute(RESET_EMAIL_KEY); | ||
session.removeAttribute(RESET_EXPIRATION_KEY); | ||
|
||
return true; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
<!DOCTYPE html> | ||
|
||
<%@ page language="java" contentType="text/html; charset=utf-8" %> | ||
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%> | ||
|
||
<html> | ||
<head> | ||
<meta charset="utf-8"> | ||
<title>Gigwa - Lost password</title> | ||
<link rel="shortcut icon" href="images/favicon.png" type="image/x-icon" /> | ||
<link type="text/css" rel="stylesheet" href="css/bootstrap.min.css"> | ||
<link type="text/css" rel="stylesheet" href="css/main.css"> | ||
<link type="text/css" rel="stylesheet" href="css/login.css"> | ||
<script type="text/javascript" src="js/jquery-1.12.4.min.js"></script> | ||
</head> | ||
<body> | ||
<div class="container"> | ||
<div class="row margin-top"> | ||
<div class="col-md-4"></div> | ||
<div class="col-md-4"> | ||
<div class="panel panel-default"> | ||
<div class="panel-body text-center"> | ||
<div style="background-color:white; padding:7px; border:darkblue 5px outset; margin:10px 0 40px 0;"><img alt="Gigwa" height="40" src="images/logo_big.png" /><br/>RESET PASSWORD</div> | ||
<form action="lostPassword.do" method="POST"> | ||
<input type="email" name="email" placeholder="Email address" required /> | ||
<c:if test="${not empty error}"> | ||
<p class="text-danger">${error}</p> | ||
</c:if> | ||
<button type="submit" class="btn btn-primary btn-block btn-large" style="margin:40px 0 20px 0;">Send reset code</button> | ||
</form> | ||
<a type="button" class="btn btn-primary btn-block btn-large margin-top-md" href="${pageContext.request.contextPath}/login.do">Return to login</a> | ||
</div> | ||
</div> | ||
</div> | ||
</div> | ||
</div> | ||
</body> | ||
</html> |
Oops, something went wrong.