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..c76fc1df 100644 --- a/src/main/java/com/iemr/common/controller/users/IEMRAdminController.java +++ b/src/main/java/com/iemr/common/controller/users/IEMRAdminController.java @@ -197,6 +197,15 @@ public String userAuthenticate( user.setUserName(mUser.get(0).getUserName()); logger.info("UserAgentUtil isMobile : " + isMobile); + // Store username → JTI mapping so concurrent-session logout can denylist this token + String accessJti = jwtUtil.getJtiFromToken(jwtToken); + redisTemplate.opsForValue().set( + "jti:" + m_User.getUserName().trim().toLowerCase(), + accessJti + "|" + mUser.get(0).getUserID(), + jwtUtil.getAccessTokenExpiration(), + TimeUnit.MILLISECONDS + ); + if (isMobile) { refreshToken = jwtUtil.generateRefreshToken(m_User.getUserName(), user.getUserID().toString()); logger.debug("Refresh token generated successfully for user: {}", user.getUserName()); @@ -387,6 +396,20 @@ public String logOutUserFromConcurrentSession( if (previousTokenFromRedis != null) { deleteSessionObjectByGettingSessionDetails(previousTokenFromRedis); sessionObject.deleteSessionObject(previousTokenFromRedis); + + // Denylist the active JWT so the first system's requests are immediately rejected + String usernameKey = mUsers.get(0).getUserName().trim().toLowerCase(); + String jtiData = (String) redisTemplate.opsForValue().get("jti:" + usernameKey); + if (jtiData != null) { + String[] parts = jtiData.split("\\|", 2); + String jti = parts[0]; + tokenDenylist.addTokenToDenylist(jti, jwtUtil.getAccessTokenExpiration()); + if (parts.length > 1) { + redisTemplate.delete("user_" + parts[1]); + } + redisTemplate.delete("jti:" + usernameKey); + } + response.setResponse("User successfully logged out"); } else{ logger.error("Unable to fetch session from redis"); @@ -522,6 +545,15 @@ public String superUserAuthenticate( isMobile = UserAgentUtil.isMobileDevice(userAgent); logger.info("UserAgentUtil isMobile : " + isMobile); + // Store username → JTI mapping so concurrent-session logout can denylist this token + String accessJti = jwtUtil.getJtiFromToken(jwtToken); + redisTemplate.opsForValue().set( + "jti:" + m_User.getUserName().trim().toLowerCase(), + accessJti + "|" + mUser.getUserID(), + jwtUtil.getAccessTokenExpiration(), + TimeUnit.MILLISECONDS + ); + if (isMobile) { refreshToken = jwtUtil.generateRefreshToken(m_User.getUserName(), user.getUserID().toString()); logger.debug("Refresh token generated successfully for user: {}", user.getUserName()); diff --git a/src/main/java/com/iemr/common/utils/JwtUtil.java b/src/main/java/com/iemr/common/utils/JwtUtil.java index 5d37a990..d7f6c270 100644 --- a/src/main/java/com/iemr/common/utils/JwtUtil.java +++ b/src/main/java/com/iemr/common/utils/JwtUtil.java @@ -163,6 +163,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