From 9a543982643f348e7082e89c3913d0c72a035dd7 Mon Sep 17 00:00:00 2001 From: Aarti Panchal Date: Wed, 13 May 2026 01:59:03 +0530 Subject: [PATCH 1/2] fix: implement 24-hour auto-unlock for account lockout after failed login attempts --- .../java/com/iemr/common/data/users/User.java | 12 +++ .../users/IEMRAdminUserServiceImpl.java | 88 ++++++++++++------- 2 files changed, 67 insertions(+), 33 deletions(-) diff --git a/src/main/java/com/iemr/common/data/users/User.java b/src/main/java/com/iemr/common/data/users/User.java index 275b0ec6..0b6e11a7 100644 --- a/src/main/java/com/iemr/common/data/users/User.java +++ b/src/main/java/com/iemr/common/data/users/User.java @@ -209,6 +209,10 @@ public class User implements Serializable { @Column(name = "failed_attempt") private Integer failedAttempt; + @Expose + @Column(name = "locked_at") + private Timestamp lockedAt; + @Expose @Column(name = "dhistoken") private String dhistoken; @@ -540,6 +544,14 @@ public String getDhistoken() { return dhistoken; } + public Timestamp getLockedAt() { + return lockedAt; + } + + public void setLockedAt(Timestamp lockedAt) { + this.lockedAt = lockedAt; + } + /* * public User(String userName, String password) { this.userName = userName; * this.password = password; } diff --git a/src/main/java/com/iemr/common/service/users/IEMRAdminUserServiceImpl.java b/src/main/java/com/iemr/common/service/users/IEMRAdminUserServiceImpl.java index 71d72c97..c6f6b3a1 100644 --- a/src/main/java/com/iemr/common/service/users/IEMRAdminUserServiceImpl.java +++ b/src/main/java/com/iemr/common/service/users/IEMRAdminUserServiceImpl.java @@ -302,15 +302,52 @@ public List userAuthenticate(String userName, String password) throws Exce } private void checkUserLoginFailedAttempt(User user) throws IEMRException { - + if (user.getDeleted() != null && user.getDeleted() && user.getLockedAt() != null) { + long lockedTime = user.getLockedAt().getTime(); + long currentTime = System.currentTimeMillis(); + long diffInMillis = currentTime - lockedTime; + long diffInHours = diffInMillis / (1000 * 60 * 60); + + // If 24 hours have passed, auto-unlock the account + if (diffInHours >= 24) { + user.setDeleted(false); + user.setFailedAttempt(0); + user.setLockedAt(null); + iEMRUserRepositoryCustom.save(user); + logger.info("User account auto-unlocked after 24 hours. User ID: {}", user.getUserID()); + } + } } private void updateUserLoginFailedAttempt(User user) throws IEMRException { + int failedAttempt = 0; + if (failedLoginAttempt != null) + failedAttempt = Integer.parseInt(failedLoginAttempt); + else + failedAttempt = 5; + + user.setFailedAttempt(user.getFailedAttempt() != null ? user.getFailedAttempt() + 1 : 1); + + if (user.getFailedAttempt() >= failedAttempt) { + user.setDeleted(true); + user.setLockedAt(new java.sql.Timestamp(System.currentTimeMillis())); + logger.warn("User Account has been locked after reaching the limit of {} failed login attempts. User ID: {}, Lock time: {}", + failedAttempt, user.getUserID(), user.getLockedAt()); + } else { + logger.warn("Failed login attempt {} of {} for user ID: {}", + user.getFailedAttempt(), failedAttempt, user.getUserID()); + } + iEMRUserRepositoryCustom.save(user); } private void resetUserLoginFailedAttempt(User user) throws IEMRException { - + if (user.getFailedAttempt() != null && user.getFailedAttempt() > 0) { + user.setFailedAttempt(0); + user.setLockedAt(null); + iEMRUserRepositoryCustom.save(user); + logger.info("User failed login attempts reset. User ID: {}", user.getUserID()); + } } /** @@ -323,17 +360,20 @@ public User superUserAuthenticate(String userName, String password) throws Excep if (users.size() != 1) { throw new IEMRException("Invalid username or password"); } - int failedAttempt = 0; - if (failedLoginAttempt != null) - failedAttempt = Integer.parseInt(failedLoginAttempt); - else - failedAttempt = 5; + User user = users.get(0); + + // Check if user account should be auto-unlocked after 24 hours + checkUserLoginFailedAttempt(user); + try { int validatePassword; validatePassword = securePassword.validatePassword(password, user.getPassword()); if (validatePassword == 1) { checkUserAccountStatus(user); + // Reset failed login attempts on successful password validation + resetUserLoginFailedAttempt(user); + int iterations = 1001; char[] chars = password.toCharArray(); byte[] salt = getSalt(); @@ -348,37 +388,19 @@ public User superUserAuthenticate(String userName, String password) throws Excep } else if (validatePassword == 2) { checkUserAccountStatus(user); + // Reset failed login attempts on successful password validation + resetUserLoginFailedAttempt(user); iEMRUserRepositoryCustom.save(user); } else if (validatePassword == 0) { - if (user.getFailedAttempt() + 1 < failedAttempt) { - user.setFailedAttempt(user.getFailedAttempt() + 1); - user = iEMRUserRepositoryCustom.save(user); - logger.warn("User Password Wrong"); - throw new IEMRException("Invalid username or password"); - } else if (user.getFailedAttempt() + 1 >= failedAttempt) { - user.setFailedAttempt(user.getFailedAttempt() + 1); - user.setDeleted(true); - user = iEMRUserRepositoryCustom.save(user); - logger.warn("User Account has been locked after reaching the limit of {} failed login attempts.", - ConfigProperties.getInteger("failedLoginAttempt")); - - throw new IEMRException( - "Invalid username or password. Please contact administrator."); - } else { - user.setFailedAttempt(user.getFailedAttempt() + 1); - user = iEMRUserRepositoryCustom.save(user); - logger.warn("Failed login attempt {} of {} for a user account.", - user.getFailedAttempt(), ConfigProperties.getInteger("failedLoginAttempt")); - throw new IEMRException( - "Invalid username or password. Please contact administrator."); - } + // Update failed login attempts and lock account if threshold reached + updateUserLoginFailedAttempt(user); + logger.warn("User Password Wrong. Failed attempts: {}", user.getFailedAttempt()); + throw new IEMRException("Invalid username or password"); } else { checkUserAccountStatus(user); - if (user.getFailedAttempt() != 0) { - user.setFailedAttempt(0); - user = iEMRUserRepositoryCustom.save(user); - } + // Reset failed login attempts on successful login + resetUserLoginFailedAttempt(user); } } catch (Exception e) { throw new IEMRException(e.getMessage()); From bb3dad43521d3210337a9388f4eecd55f4abfdd5 Mon Sep 17 00:00:00 2001 From: Aarti Panchal Date: Wed, 13 May 2026 02:15:35 +0530 Subject: [PATCH 2/2] fix: add admin manual unlock endpoint for locked user accounts --- .../controller/users/IEMRAdminController.java | 16 ++++++++++ .../users/IEMRAdminUserServiceImpl.java | 32 +++++++++++++++++++ 2 files changed, 48 insertions(+) diff --git a/src/main/java/com/iemr/common/controller/users/IEMRAdminController.java b/src/main/java/com/iemr/common/controller/users/IEMRAdminController.java index 554500f3..9196adbc 100644 --- a/src/main/java/com/iemr/common/controller/users/IEMRAdminController.java +++ b/src/main/java/com/iemr/common/controller/users/IEMRAdminController.java @@ -1248,4 +1248,20 @@ public ResponseEntity checkUserDetails(@PathVariable("userName") String userN } } + + @Operation(summary = "Manually unlock a user account") + @RequestMapping(value = "/unlockUserAccount", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON, headers = "Authorization") + public String unlockUserAccount( + @RequestParam("userID") Long userID) { + OutputResponse response = new OutputResponse(); + try { + logger.info("Unlocking user account for userID: {}", userID); + String resultMessage = iemrAdminUserServiceImpl.manuallyUnlockUserAccount(userID); + response.setResponse(resultMessage); + } catch (Exception e) { + logger.error("Error unlocking user account with ID: " + userID, e); + response.setError(e); + } + return response.toString(); + } } diff --git a/src/main/java/com/iemr/common/service/users/IEMRAdminUserServiceImpl.java b/src/main/java/com/iemr/common/service/users/IEMRAdminUserServiceImpl.java index c6f6b3a1..84fbc5d1 100644 --- a/src/main/java/com/iemr/common/service/users/IEMRAdminUserServiceImpl.java +++ b/src/main/java/com/iemr/common/service/users/IEMRAdminUserServiceImpl.java @@ -1252,4 +1252,36 @@ public List findUserIdByUserName(String userName) { return iEMRUserRepositoryCustom.findUserName(userName); } + + /** + * Manually unlock a user account by resetting failed login attempts and lock timestamp + * @param userID - The ID of the user to unlock + * @return - Success message + * @throws IEMRException - If user is not found or operation fails + */ + @Override + public String manuallyUnlockUserAccount(Long userID) throws IEMRException { + try { + User user = iEMRUserRepositoryCustom.findByUserID(userID); + + if (user == null) { + throw new IEMRException("User not found with ID: " + userID); + } + + // Reset the account lock + user.setDeleted(false); + user.setFailedAttempt(0); + user.setLockedAt(null); + + iEMRUserRepositoryCustom.save(user); + logger.info("User account manually unlocked. User ID: {}, User Name: {}", userID, user.getUserName()); + + return "User account with ID " + userID + " has been successfully unlocked."; + } catch (IEMRException e) { + throw e; + } catch (Exception e) { + logger.error("Error unlocking user account with ID: " + userID, e); + throw new IEMRException("Error unlocking user account: " + e.getMessage(), e); + } + } }