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 701add24..b3644743 100644 --- a/src/main/java/com/iemr/common/controller/users/IEMRAdminController.java +++ b/src/main/java/com/iemr/common/controller/users/IEMRAdminController.java @@ -36,6 +36,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; @@ -95,6 +96,8 @@ public class IEMRAdminController { private CookieUtil cookieUtil; @Autowired private RedisTemplate redisTemplate; + @Autowired + private StringRedisTemplate stringRedisTemplate; private AESUtil aesUtil; @@ -168,7 +171,88 @@ public String userAuthenticate( } String decryptPassword = aesUtil.decrypt("Piramal12Piramal", m_User.getPassword()); - List mUser = iemrAdminUserServiceImpl.userAuthenticate(m_User.getUserName(), decryptPassword); + // Fetch user + List existingUser = iemrAdminUserServiceImpl.userExitsCheck(m_User.getUserName()); + + /* + * ========================================= + * ACCOUNT LOCK CHECK + * ========================================= + */ + if(!existingUser.isEmpty()){ + if (existingUser.get(0) != null + && existingUser.get(0).getFailedAttempt() != null + && existingUser.get(0).getFailedAttempt() >= 5) { + + throw new IEMRException( + "Your account has been locked due to multiple failed login attempts. Please contact administrator."); + } + } + + + List mUser = iemrAdminUserServiceImpl + .userAuthenticate(m_User.getUserName(), decryptPassword); + + /* + * ========================================= + * FAILED LOGIN ATTEMPT LOGIC + * ========================================= + */ + if(!existingUser.isEmpty()){ + if (existingUser != null) { + + Integer failedAttempt = existingUser.get(0).getFailedAttempt() != null + ? existingUser.get(0).getFailedAttempt() + : 0; + + failedAttempt++; + + existingUser.get(0).setFailedAttempt(failedAttempt); + + iemrAdminUserServiceImpl.save(existingUser.get(0)); + + int remainingAttempts = 5 - failedAttempt; + + // Lock account on 5th attempt + if (failedAttempt >= 5) { + + + + response.setError(new IEMRException( + "Your account has been locked due to multiple failed login attempts.")); + return response.toString(); + } + + // Warning on 3rd attempt + if (failedAttempt == 4) { + + + response.setError(new IEMRException( + "Invalid username or password. Remaining attempts: " + + remainingAttempts + + ". If you enter wrong username or password again, your account will be locked.")); + return response.toString(); + } + + + response.setError(new IEMRException( + "Invalid username or password. Remaining attempts: " + + remainingAttempts)); + return response.toString(); + + } + } + + /* + * ========================================= + * RESET FAILED ATTEMPTS ON SUCCESS LOGIN + * ========================================= + */ + User loggedInUser = mUser.get(0); + + loggedInUser.setFailedAttempt(0); + + iemrAdminUserServiceImpl.save(loggedInUser); JSONObject resMap = new JSONObject(); JSONObject serviceRoleMultiMap = new JSONObject(); JSONObject serviceRoleMap = new JSONObject(); @@ -190,15 +274,26 @@ public String userAuthenticate( String jwtToken = null; String refreshToken = null; if (mUser.size() == 1) { - jwtToken = jwtUtil.generateToken(m_User.getUserName(), mUser.get(0).getUserID().toString()); + String userIdStr = mUser.get(0).getUserID().toString(); + jwtToken = isMobile + ? jwtUtil.generateSecureToken(userIdStr) + : jwtUtil.generateToken(m_User.getUserName(), userIdStr); User user = new User(); // Assuming the Users class exists user.setUserID(mUser.get(0).getUserID()); user.setUserName(mUser.get(0).getUserName()); logger.info("UserAgentUtil isMobile : " + isMobile); + // Store username -> JTI mapping so concurrent-session logout can denylist this token + stringRedisTemplate.opsForValue().set( + "jti:" + m_User.getUserName().trim().toLowerCase(), + jwtUtil.getJtiFromToken(jwtToken) + "|" + mUser.get(0).getUserID(), + jwtUtil.getAccessTokenExpiration(), + TimeUnit.MILLISECONDS + ); + if (isMobile) { - refreshToken = jwtUtil.generateRefreshToken(m_User.getUserName(), user.getUserID().toString()); + refreshToken = jwtUtil.generateSecureRefreshToken(user.getUserID().toString()); logger.debug("Refresh token generated successfully for user: {}", user.getUserName()); String jti = jwtUtil.getJtiFromToken(refreshToken); redisTemplate.opsForValue().set( @@ -239,7 +334,6 @@ public String userAuthenticate( // Facility data for ALL users - common pattern, empty if not applicable try { if (mUser.size() == 1) { - User loggedInUser = mUser.get(0); String userRoleName = ""; if (loggedInUser.getM_UserServiceRoleMapping() != null) { for (UserServiceRoleMapping usrm : loggedInUser.getM_UserServiceRoleMapping()) { @@ -387,6 +481,19 @@ public String logOutUserFromConcurrentSession( if (previousTokenFromRedis != null) { deleteSessionObjectByGettingSessionDetails(previousTokenFromRedis); sessionObject.deleteSessionObject(previousTokenFromRedis); + + // Denylist the active JWT so System 1's requests are immediately rejected + String usernameKey = mUsers.get(0).getUserName().trim().toLowerCase(); + String jtiData = stringRedisTemplate.opsForValue().get("jti:" + usernameKey); + if (jtiData != null) { + String[] parts = jtiData.split("\\|", 2); + tokenDenylist.addTokenToDenylist(parts[0], jwtUtil.getAccessTokenExpiration()); + if (parts.length > 1) { + redisTemplate.delete("user_" + parts[1]); + } + stringRedisTemplate.delete("jti:" + usernameKey); + } + response.setResponse("User successfully logged out"); } else{ logger.error("Unable to fetch session from redis"); @@ -522,8 +629,16 @@ public String superUserAuthenticate( isMobile = UserAgentUtil.isMobileDevice(userAgent); logger.info("UserAgentUtil isMobile : " + isMobile); + // Store username -> JTI mapping so concurrent-session logout can denylist this token + stringRedisTemplate.opsForValue().set( + "jti:" + m_User.getUserName().trim().toLowerCase(), + jwtUtil.getJtiFromToken(jwtToken) + "|" + mUser.getUserID(), + jwtUtil.getAccessTokenExpiration(), + TimeUnit.MILLISECONDS + ); + if (isMobile) { - refreshToken = jwtUtil.generateRefreshToken(m_User.getUserName(), user.getUserID().toString()); + refreshToken = jwtUtil.generateSecureRefreshToken(user.getUserID().toString()); logger.debug("Refresh token generated successfully for user: {}", user.getUserName()); String jti = jwtUtil.getJtiFromToken(refreshToken); redisTemplate.opsForValue().set( diff --git a/src/main/java/com/iemr/common/service/beneficiary/RegisterBenificiaryServiceImpl.java b/src/main/java/com/iemr/common/service/beneficiary/RegisterBenificiaryServiceImpl.java index 9c6f2f96..44f29827 100644 --- a/src/main/java/com/iemr/common/service/beneficiary/RegisterBenificiaryServiceImpl.java +++ b/src/main/java/com/iemr/common/service/beneficiary/RegisterBenificiaryServiceImpl.java @@ -155,20 +155,25 @@ else if(null != benificiaryDetails.getI_bendemographics().getReligion()) identityEditDTO.setReligion(benificiaryDetails.getI_bendemographics().getReligionName()); if (null != benificiaryDetails.getOccupation()) { - identityEditDTO.setOccupationName(benificiaryDetails.getOccupation()); - } else if (null != benificiaryDetails.getI_bendemographics() && - null != benificiaryDetails.getI_bendemographics().getOccupation()) { - identityEditDTO.setOccupationName(benificiaryDetails.getI_bendemographics().getOccupation()); - } else { - identityEditDTO.setOccupationName(benificiaryDetails.getOccupationName()); + identityEditDTO.setOccupationName(benificiaryDetails.getOccupation()); + } else if (null != benificiaryDetails.getI_bendemographics().getOccupation()) { + identityEditDTO.setOccupationName(benificiaryDetails.getI_bendemographics().getOccupation()); + } else if (null != benificiaryDetails.getOccupationName()) { + identityEditDTO.setOccupationName(benificiaryDetails.getOccupationName()); + } + if (null != benificiaryDetails.getI_bendemographics().getOccupationID()) { + identityEditDTO.setOccupationId(benificiaryDetails.getI_bendemographics().getOccupationID()); } if (null != benificiaryDetails.getEducation()) { - identityEditDTO.setEducation(benificiaryDetails.getEducation()); + identityEditDTO.setEducation(benificiaryDetails.getEducation()); } else if (null != benificiaryDetails.getI_bendemographics() && - null != benificiaryDetails.getI_bendemographics().getEducationName()) { - identityEditDTO.setEducation(benificiaryDetails.getI_bendemographics().getEducationName()); - } + null != benificiaryDetails.getI_bendemographics().getEducationName()) { + identityEditDTO.setEducation(benificiaryDetails.getI_bendemographics().getEducationName()); + } + if (null != benificiaryDetails.getI_bendemographics().getEducationID()) { + identityEditDTO.setEducationId(benificiaryDetails.getI_bendemographics().getEducationID()); + } if(null != benificiaryDetails.getIncomeStatus()) identityEditDTO.setIncomeStatus(benificiaryDetails.getIncomeStatus()); else diff --git a/src/main/java/com/iemr/common/service/users/IEMRAdminUserService.java b/src/main/java/com/iemr/common/service/users/IEMRAdminUserService.java index 26b7bb15..1db05322 100644 --- a/src/main/java/com/iemr/common/service/users/IEMRAdminUserService.java +++ b/src/main/java/com/iemr/common/service/users/IEMRAdminUserService.java @@ -126,5 +126,5 @@ public List getUserServiceRoleMappingForProvider(Integ List findUserIdByUserName(String userName) throws IEMRException; - + User save(User loggedInUser); } 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..3d20be1b 100644 --- a/src/main/java/com/iemr/common/service/users/IEMRAdminUserServiceImpl.java +++ b/src/main/java/com/iemr/common/service/users/IEMRAdminUserServiceImpl.java @@ -1230,4 +1230,9 @@ public List findUserIdByUserName(String userName) { return iEMRUserRepositoryCustom.findUserName(userName); } + + @Override + public User save(User loggedInUser) { + return iEMRUserRepositoryCustom.save(loggedInUser); + } } diff --git a/src/main/java/com/iemr/common/utils/JwtUtil.java b/src/main/java/com/iemr/common/utils/JwtUtil.java index 5d37a990..98ff7b7b 100644 --- a/src/main/java/com/iemr/common/utils/JwtUtil.java +++ b/src/main/java/com/iemr/common/utils/JwtUtil.java @@ -46,6 +46,27 @@ public String generateToken(String username, String userId) { return buildToken(username, userId, "access", ACCESS_EXPIRATION_TIME); } + // Mobile login: token without PII in sub + public String generateSecureToken(String userId) { + return buildSecureToken(userId, "access", ACCESS_EXPIRATION_TIME); + } + + public String generateSecureRefreshToken(String userId) { + return buildSecureToken(userId, "refresh", REFRESH_EXPIRATION_TIME); + } + + private String buildSecureToken(String userId, String tokenType, long expiration) { + return Jwts.builder() + .subject(userId) + .claim("userId", userId) + .claim("token_type", tokenType) + .id(UUID.randomUUID().toString()) + .issuedAt(new Date()) + .expiration(new Date(System.currentTimeMillis() + expiration)) + .signWith(getSigningKey()) + .compact(); + } + /** * Generate a refresh token. * @@ -163,6 +184,10 @@ public long getRefreshTokenExpiration() { return REFRESH_EXPIRATION_TIME; } + public long getAccessTokenExpiration() { + return ACCESS_EXPIRATION_TIME; + } + /** * Extract user ID from JWT token in the request (checks header and cookie) * @param request the HTTP request