Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
3c5b5b8
add bengali language translation on dynamic forms
SauravBizbRolly May 6, 2026
eb9de55
Merge pull request #406 from PSMRI/feature/add_bengali_language
SauravBizbRolly May 6, 2026
2cd890c
Merge remote-tracking branch 'upstream/vbb/release-3.8.1' into sm/rel…
SauravBizbRolly May 12, 2026
bc54def
change api version
SauravBizbRolly May 12, 2026
3e0bbb7
Sn/3.8.1 (#423)
snehar-nd May 22, 2026
cbb1817
fix: use stringRedisTemplate for jti: key read/delete in concurrent s…
vishwab1 May 22, 2026
8b548d3
fix: correct exempt roles and allow superadmin concurrent sessions
vishwab1 May 22, 2026
a8102b1
Merge pull request #424 from PSMRI/vb/fixSession
snehar-nd May 22, 2026
2c23d93
Implement feature multiple login attempt
SauravBizbRolly May 29, 2026
fe3336a
Implement feature multiple login attempt
SauravBizbRolly May 29, 2026
5424887
Merge pull request #426 from PSMRI/feature/handel_multiple_attempt_pa…
SauravBizbRolly Jun 4, 2026
a6a6d3c
fix: delete camp:vanID and camp:parkingPlaceID from Redis on MMU logout
vishwab1 Jun 12, 2026
758bf36
feat: add endpoint to expose server's LAN IP address
vishwab1 Jun 16, 2026
be92d7c
fix: extented the token expiry timing
snehar-nd Jun 16, 2026
fc1deba
Merge pull request #428 from PSMRI/sn/tokenExpiry
snehar-nd Jun 16, 2026
e04714b
fix issue of feature multiple login attempt
SauravBizbRolly Jun 22, 2026
fee73b3
Merge pull request #431 from PSMRI/fix_multiple_login_issue
SauravBizbRolly Jun 22, 2026
818257c
fix issue of feature multiple login attempt
SauravBizbRolly Jun 22, 2026
a27db85
fix issue of feature multiple login attempt
SauravBizbRolly Jun 22, 2026
38e4c02
Merge pull request #432 from PSMRI/fix_login_issue_multiple_login
SauravBizbRolly Jun 22, 2026
73caab7
Merge remote-tracking branch 'origin/main' into vb/release-3.8.2
vishwab1 Jun 25, 2026
d08403f
Merge remote-tracking branch 'origin/release-3.8.1' into vb/release-3…
vishwab1 Jun 25, 2026
5a34772
Merge remote-tracking branch 'origin/release-3.8.2' into vb/release-3…
vishwab1 Jun 25, 2026
c9a2694
Merge pull request #434 from PSMRI/vb/release-3.8.2
snehar-nd Jun 25, 2026
c3013e0
Increase timing of refresh token (#435)
SauravBizbRolly Jun 26, 2026
16dc5af
fix: restore remaining-attempts lockout message from PR #431
vishwab1 Jun 26, 2026
8743f34
Merge remote-tracking branch 'origin/release-3.8.2' into vb/release-3…
vishwab1 Jun 26, 2026
865c266
fix: restore account-lockout message from PR #432
vishwab1 Jun 26, 2026
89d1da2
Merge pull request #436 from PSMRI/vb/release-3.8.2
SauravBizbRolly Jun 26, 2026
d2efa54
fix: AMM-2343 on lock/Unlock the api was returning 500 error (#437)
snehar-nd Jun 29, 2026
01a5f5f
Merge StopTB-related fixes from release-3.8.3 into release-3.8.2 (#442)
vishwab1 Jul 22, 2026
d5c079c
save fcm user token in table
SauravBizbRolly Jul 23, 2026
e9b38a2
save fcm user token in table
SauravBizbRolly Jul 23, 2026
3714b6d
save fcm user token in table
SauravBizbRolly Jul 23, 2026
251cf6f
save fcm user token in table
SauravBizbRolly Jul 23, 2026
4ef61c2
save fcm user token in table
SauravBizbRolly Jul 23, 2026
65c4f93
save fcm user token in table
SauravBizbRolly Jul 23, 2026
ac35624
save fcm user token in table
SauravBizbRolly Jul 24, 2026
4f58051
save fcm user token in table
SauravBizbRolly Jul 24, 2026
7b4b14f
save fcm user token in table
SauravBizbRolly Jul 24, 2026
865ede1
save fcm user token in table
SauravBizbRolly Jul 24, 2026
c4cfb5d
save fcm user token in table
SauravBizbRolly Jul 24, 2026
3e31eda
save fcm user token in table
SauravBizbRolly Jul 24, 2026
700e2e2
save fcm user token in table
SauravBizbRolly Jul 24, 2026
e1bde6e
Merge pull request #443 from PSMRI/feature/save_user_fcm_token
SauravBizbRolly Jul 27, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

<groupId>com.iemr.common-API</groupId>
<artifactId>common-api</artifactId>
<version>3.8.1</version>
<version>3.8.2</version>
<packaging>war</packaging>

<name>Common-API</name>
Expand Down
2 changes: 1 addition & 1 deletion src/main/environment/common_docker.properties
Original file line number Diff line number Diff line change
Expand Up @@ -218,4 +218,4 @@ otp.ratelimit.enabled=${OTP_RATELIMIT_ENABLED}
otp.ratelimit.minute-limit=${OTP_RATELIMIT_MINUTE_LIMIT}
otp.ratelimit.hour-limit=${OTP_RATELIMIT_HOUR_LIMIT}
otp.ratelimit.day-limit=${OTP_RATELIMIT_DAY_LIMIT}
generateBeneficiaryIDs-api-url={GEN_BENEFICIARY_IDS_API_URL}
generateBeneficiaryIDs-api-url=${GEN_BENEFICIARY_IDS_API_URL}
Original file line number Diff line number Diff line change
Expand Up @@ -21,16 +21,19 @@
public class FirebaseMessagingConfig {
private Logger logger = LoggerFactory.getLogger(this.getClass().getSimpleName());

@Value("${firebase.enabled:false}")
@Value("${firebase.enabled}")
private boolean firebaseEnabled;

@Value("${firebase.credential-file:}")
@Value("${firebase.credential-file}")
private String firebaseCredentialFile;


@Bean
@ConditionalOnProperty(name = "firebase.enabled", havingValue = "true")
public FirebaseMessaging firebaseMessaging() throws IOException {
logger.info("===== Initializing Firebase =====");
logger.info("firebaseEnabled={}", firebaseEnabled);
logger.info("firebaseCredentialFile={}", firebaseCredentialFile);
Comment on lines +24 to +36

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | ⚑ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== candidate files =="
fd -a 'FirebaseMessagingConfig.java|FirebaseNotificationService.java' . || true

echo "== locate property sources / firebase properties =="
rg -n "firebase\.(enabled|credential-file)|firebase" -S . --glob '!**/build/**' --glob '!**/target/**' --glob '!**/node_modules/**' | head -200 || true

echo "== relevant source =="
for f in $(fd 'FirebaseMessagingConfig.java|FirebaseNotificationService.java' .); do
  echo "--- $f ($( wc -l < "$f" ) lines) ---"
  ast-grep outline "$f" || true
  cat -n "$f"
done

Repository: PSMRI/Common-API

Length of output: 20758


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== application property files =="
git ls-files | rg '(^|/)application(\..*)?\.properties$|application(\..*)?\.yml$|.*firebase.*\.(properties|yml|yaml)$' | sort

echo "== property placeholders =="
python3 - <<'PY'
from pathlib import Path
for p in Path('.').rglob('*.properties'):
    s = p.read_text(errors='replace').splitlines()
    for i, line in enumerate(s, 1):
        if 'firebase' in line.lower() and ('enabled' in line.lower() or 'credential' in line.lower()):
            print(f"{p}:{i}:{line}")
PY

echo "== read-only `@Value` missing-property probe =="
python3 - <<'PY'
from pathlib import Path
import re

for p in Path('src/main/java').rglob('*.java'):
    text = p.read_text(errors='replace')
    for token in ('firebase.enabled', 'firebase.credential-file'):
        for m in re.finditer(r'`@Value`\([^#]*?'+ re.escape(f'${{{token}}}') + r'[^)]*\)', text, re.S):
            expr = m.group(0).replace('\n',' ')
            has_default = ':}' in expr[expr.find(token):expr.find(')')]
            owner = str(p)
            # identify containing method/class declaration roughly
            before = text[:m.start()].rsplit('\n', 6)[-1]
            enclosing = before.rsplit('.',1)[-1].rsplit('{',1)[-1].strip() or p
            print(f"{owner}: {token}: expr={expr}; has_default={has_default}; enclosing={enclosing}")
PY

Repository: PSMRI/Common-API

Length of output: 1847


Make Firebase config optional at startup and remove the duplicated @Values. Both FirebaseMessagingConfig and FirebaseNotificationService bind firebase.enabled/firebase.credential-file without defaults; if these env-substituted properties are absent, Spring fails before Firebase can be used or disabled. Add explicit defaults (@Value("${firebase.enabled:false}") / @Value("${firebase.credential-file:}")), let only the config bean own the config, and remove the duplicated fields and misleading per-request β€œInitializing Firebase” log from the service.

πŸ“ Affects 2 files
  • src/main/java/com/iemr/common/config/firebase/FirebaseMessagingConfig.java#L24-L36 (this comment)
  • src/main/java/com/iemr/common/service/firebaseNotification/FirebaseNotificationService.java#L70-L82
πŸ€– Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/main/java/com/iemr/common/config/firebase/FirebaseMessagingConfig.java`
around lines 24 - 36, The Firebase configuration properties must have safe
defaults so missing environment values do not prevent startup: update the fields
in FirebaseMessagingConfig to use false for firebase.enabled and an empty value
for firebase.credential-file. Remove the duplicated property fields and related
bindings from FirebaseNotificationService, and remove its per-request
β€œInitializing Firebase” log; FirebaseMessagingConfig should be the sole owner of
these settings. Apply these changes in
src/main/java/com/iemr/common/config/firebase/FirebaseMessagingConfig.java lines
24-36 and
src/main/java/com/iemr/common/service/firebaseNotification/FirebaseNotificationService.java
lines 70-82.

if (!firebaseEnabled) {
logger.error("⚠️ Firebase disabled by config");
return null;
Expand All @@ -42,9 +45,11 @@ public FirebaseMessaging firebaseMessaging() throws IOException {
return null; // don't throw, app will still start
}

GoogleCredentials credentials = GoogleCredentials.fromStream(
new ClassPathResource(firebaseCredentialFile).getInputStream()
);
GoogleCredentials credentials =
GoogleCredentials.fromStream(
new FileInputStream(firebaseCredentialFile)
);

FirebaseOptions options = FirebaseOptions.builder()
.setCredentials(credentials)
.build();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
/*
* AMRIT – Accessible Medical Records via Integrated Technology
* Integrated EHR (Electronic Health Records) Solution
*
* Copyright (C) "Piramal Swasthya Management and Research Institute"
*
* This file is part of AMRIT.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see https://www.gnu.org/licenses/.
*/
package com.iemr.common.controller.connect;

import java.util.LinkedHashMap;
import java.util.Map;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import com.iemr.common.utils.NetworkUtil;

import io.swagger.v3.oas.annotations.Operation;

/**
* Exposes the server's LAN address so a mobile device on the same wifi
* network can connect to this API.
*/
@RestController
@RequestMapping(value = "/public/connect")
public class ConnectController {

@Value("${server.port:8080}")
private int serverPort;

@Operation(summary = "Get the server's LAN IP address and port")
@GetMapping(value = "/info", produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<Map<String, Object>> getConnectInfo() {
Map<String, Object> response = new LinkedHashMap<>();
response.put("ip", NetworkUtil.getLanIPAddress());
response.put("port", serverPort);
return ResponseEntity.ok(response);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,12 @@
import com.iemr.common.model.notification.UserToken;
import com.iemr.common.service.firebaseNotification.FirebaseNotificationService;
import com.iemr.common.utils.exception.IEMRException;
import com.iemr.common.utils.response.ApiResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

@RestController
Expand All @@ -41,17 +44,60 @@ public class FirebaseNotificationController {
@Autowired
FirebaseNotificationService firebaseNotificationService;

@RequestMapping(value = "sendNotification",method = RequestMethod.POST,headers = "Authorization")
public String sendNotificationByToken(@RequestBody NotificationMessage notificationMessage){
return firebaseNotificationService.sendNotification(notificationMessage);

@PostMapping("/sendNotification")
public ResponseEntity<String> sendNotificationByToken(@RequestBody NotificationMessage notificationMessage) {

logger.info("Received notification request. Token={}, Title={}, ReceiverId={}, Type={}",
notificationMessage.getToken(),
notificationMessage.getTitle(),
notificationMessage.getData() != null ? notificationMessage.getData().get("receiver_user_id") : null,
notificationMessage.getData() != null ? notificationMessage.getData().get("notification_type") : null);

logger.debug("Notification payload: {}", notificationMessage);

try {
String response = firebaseNotificationService.sendNotification(notificationMessage);

logger.info("Notification processed successfully. Response={}", response);

return ResponseEntity.ok(response);

} catch (Exception e) {

logger.error("Failed to process notification request. Token={}, ReceiverId={}, Error={}",
notificationMessage.getToken(),
notificationMessage.getData() != null ? notificationMessage.getData().get("receiver_user_id") : null,
e.getMessage(),
e);

return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body("Failed to send notification");
}
}

@RequestMapping(value = "updateToken",method = RequestMethod.POST,headers = "Authorization")
public String updateToken(@RequestBody UserToken userToken){
return firebaseNotificationService.updateToken(userToken);
@PostMapping("/updateToken")
public ResponseEntity<?> updateToken(@RequestBody UserToken userToken) {
try {

Object result = firebaseNotificationService.updateToken(userToken);

return ResponseEntity.ok(result);

} catch (IllegalArgumentException e) {

return ResponseEntity.badRequest().body(e.getMessage());

} catch (Exception e) {

logger.error("Error while updating Firebase token", e);

return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body("Failed to update Firebase token.");
}
}

@RequestMapping(value = "getToken",method = RequestMethod.GET,headers = "Authorization")
@PostMapping(value = "getToken")
public String getUserToken() throws IEMRException {

return firebaseNotificationService.getUserToken();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@
private static final String USER_ID_FIELD = "userId";
private final Logger logger = LoggerFactory.getLogger(this.getClass().getName());
private InputMapper inputMapper = new InputMapper();
private static final Set<String> CONCURRENT_SESSION_EXEMPT_ROLES = Set.of("provideradmin", "superadmin");

// @Value("${captcha.enable-captcha}")
private boolean enableCaptcha =false;
Expand Down Expand Up @@ -172,7 +173,17 @@
}

String decryptPassword = aesUtil.decrypt("Piramal12Piramal", m_User.getPassword());
List<User> mUser = iemrAdminUserServiceImpl.userAuthenticate(m_User.getUserName(), decryptPassword);


List<User> mUser = iemrAdminUserServiceImpl
.userAuthenticate(m_User.getUserName(), decryptPassword);


User loggedInUser = mUser.get(0);

loggedInUser.setFailedAttempt(0);

iemrAdminUserServiceImpl.save(loggedInUser);
Comment on lines +182 to +186

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

πŸ”’ Security & Privacy | 🟠 Major | ⚑ Quick win

Remove the duplicate stale account-state write. Successful authentication already clears and persists failed-attempt state in IEMRAdminUserServiceImpl; this later save can overwrite a concurrent failed-login/lock update.

  • src/main/java/com/iemr/common/controller/users/IEMRAdminController.java#L182-L186: do not reset and save the returned user again.
  • src/main/java/com/iemr/common/service/users/IEMRAdminUserService.java#L136-L136: remove the raw save(User) contract.
  • src/main/java/com/iemr/common/service/users/IEMRAdminUserServiceImpl.java#L1388-L1391: remove the corresponding repository delegation.
πŸ“ Affects 3 files
  • src/main/java/com/iemr/common/controller/users/IEMRAdminController.java#L182-L186 (this comment)
  • src/main/java/com/iemr/common/service/users/IEMRAdminUserService.java#L136-L136
  • src/main/java/com/iemr/common/service/users/IEMRAdminUserServiceImpl.java#L1388-L1391
πŸ€– Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/main/java/com/iemr/common/controller/users/IEMRAdminController.java`
around lines 182 - 186, Remove the redundant failed-attempt reset and save after
successful authentication in IEMRAdminController.java:182-186, leaving
account-state persistence to IEMRAdminUserServiceImpl. Remove the raw save(User)
contract from IEMRAdminUserService.java:136 and its repository delegation from
IEMRAdminUserServiceImpl.java:1388-1391; update any resulting references as
needed.

JSONObject resMap = new JSONObject();
JSONObject serviceRoleMultiMap = new JSONObject();
JSONObject serviceRoleMap = new JSONObject();
Expand All @@ -181,11 +192,22 @@
if (m_User.getUserName() != null
&& (m_User.getDoLogout() == null || !m_User.getDoLogout())
&& (m_User.getWithCredentials() != null && m_User.getWithCredentials())) {
String tokenFromRedis = getConcurrentCheckSessionObjectAgainstUser(
m_User.getUserName().trim().toLowerCase());
if (tokenFromRedis != null) {
throw new IEMRException(
"You are already logged in,please confirm to logout from other device and login again");
String userRole = "";
if (mUser.size() == 1 && mUser.get(0).getM_UserServiceRoleMapping() != null) {
for (UserServiceRoleMapping usrm : mUser.get(0).getM_UserServiceRoleMapping()) {
if (usrm.getM_Role() != null && usrm.getM_Role().getRoleName() != null) {
userRole = usrm.getM_Role().getRoleName();
break;
}
}
}
if (!CONCURRENT_SESSION_EXEMPT_ROLES.contains(userRole.trim().toLowerCase())) {
String tokenFromRedis = getConcurrentCheckSessionObjectAgainstUser(
m_User.getUserName().trim().toLowerCase());
if (tokenFromRedis != null) {
throw new IEMRException(
"You are already logged in,please confirm to logout from other device and login again");
}
Comment on lines +195 to +210

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

πŸ”’ Security & Privacy | 🟠 Major | ⚑ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

rg -n -C 5 'getUserServiceRoleMapping\s*\(|userExitsCheck\s*\(|CONCURRENT_SESSION_EXEMPT_ROLES' \
  src/main/java/com/iemr/common

rg -n -C 5 'IEMRAdminController|concurrent.*session|provideradmin|superadmin' \
  src/test/java

Repository: PSMRI/Common-API

Length of output: 38446


🏁 Script executed:

#!/bin/bash
set -euo pipefail

sed -n '175,220p' src/main/java/com/iemr/common/controller/users/IEMRAdminController.java
printf '\n----\n'
sed -n '410,470p' src/main/java/com/iemr/common/controller/users/IEMRAdminController.java
printf '\n----\n'
sed -n '700,760p' src/main/java/com/iemr/common/service/users/IEMRAdminUserServiceImpl.java
printf '\n----\n'
sed -n '1328,1395p' src/main/java/com/iemr/common/service/users/IEMRAdminUserServiceImpl.java

Repository: PSMRI/Common-API

Length of output: 10532


🏁 Script executed:

#!/bin/bash
set -euo pipefail

rg -n -C 4 'findByUserName\s*\(|userExitsCheck\s*\(' src/main/java/com/iemr/common

printf '\n----\n'
rg -n -C 6 'interface .*RepositoryCustom|class .*RepositoryCustom|findByUserName' src/main/java/com/iemr/common

printf '\n----\n'
sed -n '1,220p' src/main/java/com/iemr/common/repository/users/IEMRUserRepositoryCustom.java

Repository: PSMRI/Common-API

Length of output: 39428


Use the complete role mappings for concurrent-session checks.

  • src/main/java/com/iemr/common/controller/users/IEMRAdminController.java#L195-L210: this branch only inspects the first mapping, but getUserServiceRoleMapping() iterates a Set<Object[]>, so the chosen role can vary for multi-role users.
  • src/main/java/com/iemr/common/controller/users/IEMRAdminController.java#L437-L458: reload the mappings before applying the same check; userExitsCheck() returns users only, so this path should not depend on an already populated role collection.

Define whether an exempt role should match any mapping or require all mappings to be exempt, then add a multi-role test.

πŸ“ Affects 1 file
  • src/main/java/com/iemr/common/controller/users/IEMRAdminController.java#L195-L210 (this comment)
  • src/main/java/com/iemr/common/controller/users/IEMRAdminController.java#L437-L458
πŸ€– Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/main/java/com/iemr/common/controller/users/IEMRAdminController.java`
around lines 195 - 210, The concurrent-session exemption currently inspects only
the first role mapping and may use an unpopulated collection. Update the checks
around IEMRAdminController’s login flow and the path near userExitsCheck() to
reload and inspect complete role mappings, defining and consistently applying
whether any or all roles must be exempt; add a multi-role test covering the
chosen behavior. Both affected sites require the same complete-mapping logic.

}
} else if (m_User.getUserName() != null && m_User.getDoLogout() != null && m_User.getDoLogout() == true) {
deleteSessionObject(m_User.getUserName().trim().toLowerCase());
Expand Down Expand Up @@ -254,7 +276,6 @@
// 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()) {
Expand Down Expand Up @@ -413,16 +434,28 @@
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]);
String userRole = "";
if (mUsers.get(0).getM_UserServiceRoleMapping() != null) {
for (UserServiceRoleMapping usrm : mUsers.get(0).getM_UserServiceRoleMapping()) {
if (usrm.getM_Role() != null && usrm.getM_Role().getRoleName() != null) {
userRole = usrm.getM_Role().getRoleName();
break;
}
}
}
if (!CONCURRENT_SESSION_EXEMPT_ROLES.contains(userRole.trim().toLowerCase())) {
// Denylist the active JWT so the first system'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);
String jti = parts[0];
tokenDenylist.addTokenToDenylist(jti, jwtUtil.getAccessTokenExpiration());
if (parts.length > 1) {
redisTemplate.delete("user_" + parts[1]);
}
stringRedisTemplate.delete("jti:" + usernameKey);
}
stringRedisTemplate.delete("jti:" + usernameKey);
}

response.setResponse("User successfully logged out");
Expand Down Expand Up @@ -537,11 +570,13 @@
String refreshToken = null;
boolean isMobile = false;
if (m_User.getUserName() != null && (m_User.getDoLogout() == null || m_User.getDoLogout() == false)) {
String tokenFromRedis = getConcurrentCheckSessionObjectAgainstUser(
m_User.getUserName().trim().toLowerCase());
if (tokenFromRedis != null) {
throw new IEMRException(
"You are already logged in,please confirm to logout from other device and login again");
if (!CONCURRENT_SESSION_EXEMPT_ROLES.contains(m_User.getUserName().trim().toLowerCase())) {
String tokenFromRedis = getConcurrentCheckSessionObjectAgainstUser(
m_User.getUserName().trim().toLowerCase());
if (tokenFromRedis != null) {
throw new IEMRException(
"You are already logged in,please confirm to logout from other device and login again");
}
}
} else if (m_User.getUserName() != null && m_User.getDoLogout() != null && m_User.getDoLogout() == true) {
deleteSessionObject(m_User.getUserName().trim().toLowerCase());
Expand Down Expand Up @@ -1000,6 +1035,13 @@
try {
deleteSessionObjectByGettingSessionDetails(request.getHeader("Authorization"));
sessionObject.deleteSessionObject(request.getHeader("Authorization"));
try {

Check warning on line 1038 in src/main/java/com/iemr/common/controller/users/IEMRAdminController.java

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Extract this nested try block into a separate method.

See more on https://sonarcloud.io/project/issues?id=PSMRI_Common-API&issues=AZ8YVvgU-_kpGiYTbzWU&open=AZ8YVvgU-_kpGiYTbzWU&pullRequest=438
stringRedisTemplate.delete("camp:vanID");
stringRedisTemplate.delete("camp:parkingPlaceID");
logger.info("Camp config cleared from Redis on MMU logout");
} catch (Exception redisEx) {
logger.warn("Failed to clear camp Redis keys on logout: {}", redisEx.getMessage());
}
response.setResponse("Success");
} catch (Exception e) {
response.setError(e);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@ public class FormFieldOption {
@Column(name = "label_as")
private String labelAs;

@Column(name = "label_bn")
private String labelBn;

@Column(name = "sort_order")
private Integer sortOrder;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ public class Translation {
private String hindiTranslation;
@Column(name = "assamese_translation")
private String assameseTranslation;
@Column(name = "bengali_translation")
private String bengaliTranslation;
@Column(name = "is_active")
private Boolean isActive;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,25 +22,26 @@
/*
* AMRIT – Accessible Medical Records via Integrated Technology
*/
package com.iemr.common.data.userToken;

Check warning on line 25 in src/main/java/com/iemr/common/data/userToken/UserFcmTokenData.java

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Rename this package name to match the regular expression '^[a-z_]+(\.[a-z_][a-z0-9_]*)*$'.

See more on https://sonarcloud.io/project/issues?id=PSMRI_Common-API&issues=AZ-iT6rHV5HiXmLbBI8v&open=AZ-iT6rHV5HiXmLbBI8v&pullRequest=438

import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
import jakarta.persistence.*;
import lombok.Data;

import java.sql.Timestamp;

import static jakarta.persistence.GenerationType.IDENTITY;

@Entity
@Table(name = "user_tokens", schema = "db_iemr")
@Table(name = "user_fcm_tokens", schema = "db_iemr")
@Data
public class UserTokenData {
public class UserFcmTokenData {
@Id
@GeneratedValue(strategy = IDENTITY)
private int id;
@Column(name = "user_id")
Integer userId;
private Integer userId;
@Column(name = "token")
String token;
private String token;
@Column(name = "updated_at")
Timestamp updatedAt;
private Timestamp updatedAt;
Comment on lines 34 to +46

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

πŸ—„οΈ Data Integrity & Integration | 🟠 Major | ⚑ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Look for migration scripts referencing the renamed table
rg -n "user_fcm_tokens|user_tokens" -g '*.sql' -g '*.xml' -g '*.yml'
fd -i changelog

Repository: PSMRI/Common-API

Length of output: 154


🏁 Script executed:

#!/bin/bash
set -u

echo "== files matching migration-related names =="
git ls-files | rg -n -i 'liquibase|flyway|migration|changelog|db|schema|sql$|yaml|yml' | head -200

echo
echo "== tracking all references to token tables across tracked files =="
rg -n -i "user_fcm_tokens|user_tokens|firebase|fcm" . \
  -g '!**/target/**' -g '!**/build/**' -g '!**/.git/**' -g '!**/node_modules/**' \
  | head -200

echo
echo "== candidate data files =="
fd -i 'UserFcmTokenData.java|FirebaseNotificationService.java' .

Repository: PSMRI/Common-API

Length of output: 17601


🏁 Script executed:

#!/bin/bash
set -u

echo "== UserFcmTokenData =="
cat -n src/main/java/com/iemr/common/data/userToken/UserFcmTokenData.java

echo
echo "== UserFcmTokenRepo =="
cat -n src/main/java/com/iemr/common/repo/userToken/UserFcmTokenRepo.java

echo
echo "== FirebaseNotificationService relevant method =="
sed -n '120,165p' src/main/java/com/iemr/common/service/firebaseNotification/FirebaseNotificationService.java | cat -n

echo
echo "== targeted table references in all tracked files =="
python3 - <<'PY'
import subprocess
result=subprocess.run(["git","ls-files","*"],capture_output=True,text=True,check=True)
terms=("user_fcm_tokens","user_tokens","firebase","fcm")
for line in result.stdout.splitlines():
    if not line.startswith(".") or line.startswith(".github"):
        try:
            data=open(line,errors="ignore").read()
        except Exception:
            continue
        if any(t in data for t in terms):
            if "user_fcm_tokens" in data or "user_tokens" in data:
                print(f"\n--- {line} ---")
                for i,l in enumerate(data.splitlines(),1):
                    if "user_fcm_tokens" in l or "user_tokens" in l:
                        print(f"{i}: {l}")
PY

Repository: PSMRI/Common-API

Length of output: 5484


Add a migration for the new FCM token schema.

UserFcmTokenData now maps to db_iemr.user_fcm_tokens with an auto-generated id and no user_id uniqueness, but there are no repo SQL migrations creating this table/columns or moving existing user_tokens rows. Add a migration before relying on this entity in already-deployed environments.

Also add a DB-unique constraint on user_id because findByUserId(...) plus the check-then-save workflow in FirebaseNotificationService.updateToken() can otherwise allow duplicate token rows per user.

πŸ€– Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/main/java/com/iemr/common/data/userToken/UserFcmTokenData.java` around
lines 34 - 46, Add a database migration for the schema represented by
UserFcmTokenData, creating db_iemr.user_fcm_tokens with an auto-generated id,
user_id, token, and updated_at columns, and migrate existing rows from
user_tokens. Add a unique constraint on user_id to enforce one token row per
user and ensure the migration supports already-deployed environments before
FirebaseNotificationService.updateToken() uses the entity.

}
Loading
Loading