Skip to content

Conversation

@5Amogh
Copy link
Member

@5Amogh 5Amogh commented Jan 7, 2026

Summary by CodeRabbit

Release Notes

  • New Features

    • Implemented role-based access control across clinical modules to restrict feature access by user role
    • Added doctor signature tracking capability for clinical documentation and compliance
  • Enhancements

    • Upgraded security framework with advanced authentication and authorization mechanisms
    • Improved data tracking by capturing creation metadata for clinical records

✏️ Tip: You can customize this high-level summary in your review settings.

5Amogh and others added 30 commits July 21, 2025 11:57
* story: amm-1668 task - 1754 dto updated

* story: amm-1668 task - 1754
…tData. (#96)

* Update version in pom.xml to 3.4.0

* story: amm-1668 task - 1754

* story: amm-1668 task - 1754 dto updated (#92)

* story: amm-1668 task - 1754 dto updated (#93)

* story: amm-1668 task - 1754 dto updated

* story: amm-1668 task - 1754

* fix: amm-1879 doctor signature was not coming for ncdcare

---------

Co-authored-by: Amoghavarsh <93114621+5Amogh@users.noreply.github.com>
Co-authored-by: 5Amogh <amoghavarsh@navadhiti.com>
fix: aam-1896 prescribed quantity was not coming in the casesheet
3.4.0 to 3.4.1
fix: amm-1919 fix for update doctor data for higher refferal data
Fix the WASA Issue : IDOR Vulnerability
* fix: amm-1927 res headers based on origin via allowed cors

* fix: amm-1927 coderabbit comments resolved

* localhost regex added

* Update regex pattern for localhost in interceptor
* fix: add @PreAuthorize to RBAC

* fix: wasa RBAC implementation

* fix: remove duplicate dependency

* fix: coderabbit comments

* fix: update role

* fix: enable the request matcher
snehar-nd and others added 25 commits December 4, 2025 12:22
fix: amm-1963 frequncy is not calculating for single dose frequency
Debug/remove user agent validation
Implement Role-Based Access Control with JWT and Auth Integration
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 7, 2026

📝 Walkthrough

Walkthrough

This PR implements comprehensive Spring Security integration across the codebase, adding role-based access control via @PreAuthorize annotations to controllers, introducing new security filters and handlers for JWT/authentication token validation, propagating a doctorSignatureFlag through beneficiary flow updates, and enhancing utilities for cookie, JWT token, and role management.

Changes

Cohort / File(s) Summary
Security Framework & Configuration
pom.xml, src/main/java/com/iemr/tm/utils/mapper/SecurityConfig.java, src/main/java/com/iemr/tm/utils/mapper/RoleAuthenticationFilter.java
Bumps project version 3.4.0 → 3.6.0; adds spring-boot-starter-security dependency. Introduces SecurityConfig with stateless session management, custom exception handlers, filter chain ordering, and CSRF/CORS configuration. Adds RoleAuthenticationFilter for extracting/validating user identity from JWT or Redis-backed auth tokens, loading roles from cache or database, and setting SecurityContext.
Custom Security Handlers & Entry Points
src/main/java/com/iemr/tm/utils/exception/CustomAuthenticationEntryPoint.java, src/main/java/com/iemr/tm/utils/exception/CustomAccessDeniedHandler.java
Adds CustomAuthenticationEntryPoint (401 responses) and CustomAccessDeniedHandler (403 responses) with JSON error serialization for authentication/authorization failures.
JWT & Authentication Utilities
src/main/java/com/iemr/tm/utils/JwtUtil.java, src/main/java/com/iemr/tm/utils/CookieUtil.java, src/main/java/com/iemr/tm/utils/JwtAuthenticationUtil.java, src/main/java/com/iemr/tm/utils/Constants.java
Exposes extractAllClaims to public; adds getUserIdFromToken method. Refactors getCookieValue to static; adds getAuthTokenFromCookie. Adds getUserRoles method with role validation. Introduces JWT_TOKEN, USER_AGENT, OKHTTP constants.
HTTP & Security Interceptor Updates
src/main/java/com/iemr/tm/utils/http/HTTPRequestInterceptor.java, src/main/java/com/iemr/tm/utils/http/HttpUtils.java, src/main/java/com/iemr/tm/utils/JwtUserIdValidationFilter.java, src/main/java/com/iemr/tm/utils/RestTemplateUtil.java
Adds configurable CORS origin validation with regex pattern matching; enhances error responses with conditional origin headers. Integrates JWT token preparation into HttpUtils. Strengthens JwtUserIdValidationFilter with strict CORS handling for OPTIONS, origin validation logging, mobile client detection, and cookie cleanup. Adds extractJwttoken helper and getJwttokenFromHeaders for request enrichment.
Redis Role Caching
src/main/java/com/iemr/tm/utils/redis/RedisStorage.java
Adds cacheUserRoles (30-min TTL) and getUserRoleFromCache methods for role caching keyed on userId.
ANC Controller Security
src/main/java/com/iemr/tm/controller/anc/AntenatalCareController.java
Applies @PreAuthorize annotations requiring NURSE or DOCTOR roles across 14 ANC endpoints (save, get, update operations).
Cancer Screening, COVID-19, NCD Care, NCD Screening Controllers
src/main/java/com/iemr/tm/controller/cancerscreening/CancerScreeningController.java, src/main/java/com/iemr/tm/controller/covid19/CovidController.java, src/main/java/com/iemr/tm/controller/ncdCare/NCDCareController.java, src/main/java/com/iemr/tm/controller/ncdscreening/NCDScreeningController.java
Each controller receives @PreAuthorize on multiple endpoints; some allow NURSE, DOCTOR, ONCOLOGIST, or role combinations. Enforces role-based access for save/get/update operations.
General OPD, PNC, Quick Consult Controllers
src/main/java/com/iemr/tm/controller/generalOPD/GeneralOPDController.java, src/main/java/com/iemr/tm/controller/pnc/PostnatalCareController.java, src/main/java/com/iemr/tm/controller/quickconsult/QuickConsultController.java
Adds @PreAuthorize on endpoints requiring NURSE and/or DOCTOR roles for data operations across OPD, postnatal care, and quick consultation endpoints.
Specialist & Registry Controllers
src/main/java/com/iemr/tm/controller/registrar/main/RegistrarController.java, src/main/java/com/iemr/tm/controller/labtechnician/LabtechnicianController.java, src/main/java/com/iemr/tm/controller/location/LocationController.java, src/main/java/com/iemr/tm/controller/snomedct/SnomedController.java
Registrar endpoints restricted to NURSE, DOCTOR, REGISTRAR, LAB_TECHNICIAN, PHARMACIST, ASHA roles. Lab technician controller requires LAB_TECHNICIAN/LABTECHNICIAN. Location controller allows 12 roles. Snomed controller requires NURSE/DOCTOR.
Administrative & Sync Controllers
src/main/java/com/iemr/tm/controller/dataSyncActivity/StartSyncActivity.java, src/main/java/com/iemr/tm/controller/dataSyncLayerCentral/MMUDataSyncVanToServer.java, src/main/java/com/iemr/tm/controller/common/master/CommonMasterController.java, src/main/java/com/iemr/tm/controller/report/CRMReportController.java, src/main/java/com/iemr/tm/controller/nurse/vitals/AnthropometryVitalsController.java, src/main/java/com/iemr/tm/controller/foetalmonitor/FoetalMonitorController.java
Data sync controllers require DATASYNC/DATA_SYNC roles. Master and vitals controllers enforce NURSE/DOCTOR. Report controller allows 9 roles (NURSE, DOCTOR, LAB_TECHNICIAN, PHARMACIST, TC_SPECIALIST, ONCOLOGIST, RADIOLOGIST, etc.).
Worklist & Patient App Controllers
src/main/java/com/iemr/tm/controller/common/main/WorklistController.java, src/main/java/com/iemr/tm/controller/patientApp/master/PatientAppCommonMasterController.java
WorklistController extensively refactored: adds Authentication parameters to multiple endpoints, replaces userID path variables with authentication-derived userID, adds JwtUtil dependency, enforces role checks for DOCTOR, NURSE, LAB_TECHNICIAN, RADIOLOGIST, etc. Patient app controller adds @PreAuthorize on endpoints requiring NURSE/DOCTOR/TC_SPECIALIST roles.
Specialized Consultation Controllers
src/main/java/com/iemr/tm/controller/teleconsultation/TeleConsultationController.java, src/main/java/com/iemr/tm/controller/videoconsultationcontroller/VideoConsultationController.java
TeleConsultation: class-level @PreAuthorize for TC_SPECIALIST roles; getTCSpecialistWorkListNew now accepts Authentication parameter and validates userID match. VideoConsultation login endpoint now requires Authentication parameter and verifies userID alignment. Both add JwtUtil dependency.
Login Controller
src/main/java/com/iemr/tm/controller/login/IemrMmuLoginController.java
Class-level @PreAuthorize restricting to multiple roles. getUserServicePointVanDetails and getUserVanSpDetails now accept Authentication parameters; extract userID from token instead of request body; validate authentication before proceeding; return 403 for unauthorized access.
Domain Model Changes
src/main/java/com/iemr/tm/data/benFlowStatus/BeneficiaryFlowStatus.java
Adds private Boolean doctorSignatureFlag (default false) with getter/setter and column mapping. Extends getBeneficiaryFlowStatusForLeftPanel to populate flag from result set when available.
NCD Diagnosis Entity
src/main/java/com/iemr/tm/data/ncdcare/NCDCareDiagnosis.java
Expands constructor to include createdBy (String) and createdDate (Timestamp) parameters. Updates getNCDCareDiagnosisDetails factory method to map additional fields from result set indices 9 and 10.
Repository Enhancement - Beneficiary Flow
src/main/java/com/iemr/tm/repo/benFlowStatus/BeneficiaryFlowStatusRepo.java
Updates 5+ methods (updateBenFlowStatusAfterDoctorActivity, ...Specialist, ...SpecialistANC, ...TCSpecialist, ...LabResultEntryForSpecialist, ...DoctorActivityUpdate) to accept @Param("signatureFlag") Boolean and set t.doctorSignatureFlag in JPQL. getBenDetailsForLeftSidePanel now selects t.doctorSignatureFlag.
Repository Enhancement - User Login
src/main/java/com/iemr/tm/repo/login/UserLoginRepo.java
Adds getRoleNamebyUserId native query method returning List of role names for a given userID.
NCD Diagnosis Repository
src/main/java/com/iemr/tm/repo/nurse/ncdcare/NCDCareDiagnosisRepo.java
getNCDCareDiagnosisDetails query now retrieves two additional columns (createdBy, createdDate) in result set.
Service Layer - Flow Status Updates
src/main/java/com/iemr/tm/service/benFlowStatus/CommonBenStatusFlowServiceImpl.java
Extends 5 public methods with Boolean signatureFlag parameter: updateBenFlowAfterDocData, ...FromSpecialist, ...FromSpecialistANC, ...DocDataUpdate, ...DocDataUpdateTCSpecialist. Parameter propagated to corresponding repository calls.
Service Layer - Doctor Transaction Management
src/main/java/com/iemr/tm/service/common/transaction/CommonDoctorServiceImpl.java
updateBenFlowtableAfterDocDataSave and updateBenFlowtableAfterDocDataUpdate now accept additional Boolean signatureFlag/doctorSignatureFlag parameter; propagated to downstream flow updates. Re-enables referral detail branch logic.
Service Layer - Discipline-Specific Updates
src/main/java/com/iemr/tm/service/anc/ANCServiceImpl.java, src/main/java/com/iemr/tm/service/covid19/Covid19ServiceImpl.java, src/main/java/com/iemr/tm/service/generalOPD/GeneralOPDServiceImpl.java, src/main/java/com/iemr/tm/service/ncdCare/NCDCareServiceImpl.java, src/main/java/com/iemr/tm/service/pnc/PNCServiceImpl.java, src/main/java/com/iemr/tm/service/quickConsultation/QuickConsultationServiceImpl.java
Each service introduces doctorSignatureFlag extraction from incoming request (checks for presence via .has or null-safe access) and propagates flag to updateBenFlowtableAfterDocDataSave/Update calls. Affects both save and update code paths.
Service Layer - Screening & NCD Updates
src/main/java/com/iemr/tm/service/ncdscreening/NCDScreeningServiceImpl.java, src/main/java/com/iemr/tm/service/ncdscreening/NCDSCreeningDoctorServiceImpl.java, src/main/java/com/iemr/tm/service/cancerScreening/CSServiceImpl.java
Adds doctorSignatureFlag extraction and propagation to downstream flow update methods; follows same pattern as other screening services.
Service Layer - Frequency Handling
src/main/java/com/iemr/tm/service/common/transaction/CommonNurseServiceImpl.java
Expands getQtyForOneDay frequency matching to recognize additional variants (OD/BD/TID/QID with Before Food, After Food, At Bedtime modifiers); extends conditional branches for equivalent frequency string mappings affecting quantity calculations.

Sequence Diagram(s)

sequenceDiagram
    actor Client
    participant Filter as RoleAuthenticationFilter
    participant JWT as JwtUtil
    participant Redis as RedisStorage
    participant UserService as UserLoginRepo
    participant Auth as SecurityContext
    participant Controller as `@PreAuthorize`<br/>Protected Endpoint
    participant Service as Business Logic

    Client->>Filter: HTTP Request + JWT/Auth Token
    Filter->>JWT: validateToken(token)
    alt JWT Valid
        JWT-->>Filter: userId extracted
    else JWT Invalid
        Filter->>Redis: getSession(authToken)
        alt Redis Session Exists
            Redis-->>Filter: userID from session JSON
        else No Session
            Filter->>Auth: clear SecurityContext
            Filter->>Service: continue (unauthenticated)
            Service-->>Client: 401 Unauthorized
            Note over Client,Service: Request blocked
        end
    end
    
    Filter->>Redis: getUserRoleFromCache(userId)
    alt Cache Hit
        Redis-->>Filter: [roles]
    else Cache Miss
        Filter->>UserService: getRoleNamebyUserId(userId)
        UserService-->>Filter: [roles]
        Filter->>Redis: cacheUserRoles(userId, roles)
    end
    
    Filter->>Auth: set UsernamePasswordAuthenticationToken<br/>with GrantedAuthorities
    Auth-->>Filter: authenticated
    Filter->>Controller: proceed with Authentication
    
    Controller->>Controller: `@PreAuthorize`<br/>check roles
    alt Role Match
        Controller->>Service: invoke business logic
        Service-->>Controller: response
        Controller-->>Client: 200 OK + data
    else Role Mismatch
        Controller-->>Client: 403 Forbidden
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~75 minutes

Possibly related PRs

  • Role Based Broken Access Control Implementation : WASA #104: Implements identical Spring Security architecture changes including @PreAuthorize annotations across controllers, RoleAuthenticationFilter/SecurityConfig setup, JWT/authentication utilities, and role-based access control enforcement—directly mirrors this PR's security integration patterns.

Suggested reviewers

  • drtechie

Poem

🐰 A rabbit hops through Spring's new gates,
Where roles and tokens orchestrate,
With JWT and Redis cache so fine,
Authentication flows align,
The burrow's guard now sealed just right! 🔐✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 42.61% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title 'Release 3.6.0 into main' directly and clearly describes the primary objective of the changeset—merging the 3.6.0 release into the main branch, which aligns with the version bump shown in pom.xml.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ast-grep (0.40.3)
src/main/java/com/iemr/tm/service/common/transaction/CommonNurseServiceImpl.java

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@sonarqubecloud
Copy link

sonarqubecloud bot commented Jan 7, 2026

Quality Gate Failed Quality Gate failed

Failed conditions
1 Security Hotspot
22.8% Duplication on New Code (required ≤ 3%)
B Security Rating on New Code (required ≥ A)

See analysis details on SonarQube Cloud

Catch issues before they fail your Quality Gate with our IDE extension SonarQube for IDE


}
JSONObject obj = new JSONObject(comingRequest);
logger.info("getUserVanSpDetails request {}", comingRequest);

Check notice

Code scanning / SonarCloud

Logging should not be vulnerable to injection attacks Low

Change this code to not log user-controlled data. See more on SonarQube Cloud
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

Note

Due to the large number of review comments, Critical severity comments were prioritized as inline comments.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
src/main/java/com/iemr/tm/service/common/transaction/CommonNurseServiceImpl.java (1)

2883-3058: Fix logging typo and double-space inconsistency in frequency literals

Two issues to address:

  1. Logging bug (line 2886):

    logger.info("Frequecy", frequency);

    Typo: "Frequecy" should be "Frequency". More importantly, without a {} placeholder, the frequency parameter is silently ignored by SLF4J. Fix to:

    logger.info("Frequency: {}", frequency);
  2. Frequency string literals (line 3011):

    frequency.equalsIgnoreCase("Single Dose Before  Food")
    frequency.equalsIgnoreCase("Single Dose After  Food")

    These contain double spaces before "Food", which will only match if upstream sends the exact same typo. Change to single spaces:

    frequency.equalsIgnoreCase("Single Dose Before Food")
    frequency.equalsIgnoreCase("Single Dose After Food")
src/main/java/com/iemr/tm/service/covid19/Covid19ServiceImpl.java (1)

970-987: Fix doctorSignatureFlag extraction in saveDoctorData (currently ignores actual JSON value)

Right now:

  • boolean doctorSignature = requestOBJ.has("doctorSignatureFlag");
  • doctorSignatureFlag is set to doctorSignature, i.e., it becomes true whenever the field is present, even if the JSON value is explicitly false.
  • The .has(...) call happens before the if (requestOBJ != null) guard, so a null requestOBJ would NPE earlier than before.

This means flow-table updates may incorrectly record a signed case whenever the client merely includes the field.

You can align this with the safer pattern used in updateCovid19DoctorData:

Proposed fix
-        Long referSaveSuccessFlag = null;
-        boolean doctorSignature = requestOBJ.has("doctorSignatureFlag");
+        Long referSaveSuccessFlag = null;
@@
-            Boolean doctorSignatureFlag = false;
-            if (doctorSignature) {
-            doctorSignatureFlag = doctorSignature;
-            }
+            Boolean doctorSignatureFlag = false;
+            if (requestOBJ != null
+                    && requestOBJ.has("doctorSignatureFlag")
+                    && !requestOBJ.get("doctorSignatureFlag").isJsonNull()) {
+                doctorSignatureFlag = requestOBJ.get("doctorSignatureFlag").getAsBoolean();
+            }
@@
-                int i = commonDoctorServiceImpl.updateBenFlowtableAfterDocDataSave(commonUtilityClass, isTestPrescribed,
-                        isMedicinePrescribed, tcRequestOBJ, doctorSignatureFlag);
+                int i = commonDoctorServiceImpl.updateBenFlowtableAfterDocDataSave(
+                        commonUtilityClass, isTestPrescribed, isMedicinePrescribed, tcRequestOBJ, doctorSignatureFlag);

Also applies to: 1093-1096

src/main/java/com/iemr/tm/repo/benFlowStatus/BeneficiaryFlowStatusRepo.java (1)

254-263: Same missing space issue in updateBenFlowStatusAfterDoctorActivityTCSpecialist.

Line 258 has the same JPQL syntax error - missing space before WHERE.

🐛 Proposed fix
	@Query("UPDATE BeneficiaryFlowStatus t set t.pharmacist_flag = :pharmaFlag, "
			+ " t.oncologist_flag = :oncologistFlag, t.processed = 'U', t.specialist_flag = :tcSpecialistFlag, "
-			+ "t.lab_technician_flag = :labTechnicianFlag, t.doctorSignatureFlag = :signatureFlag"
+			+ "t.lab_technician_flag = :labTechnicianFlag, t.doctorSignatureFlag = :signatureFlag "
			+ " WHERE t.benFlowID = :benFlowID AND  t.beneficiaryRegID = :benRegID AND t.beneficiaryID = :benID ")
🤖 Fix all issues with AI agents
In @src/main/java/com/iemr/tm/utils/http/HttpUtils.java:
- Around line 63-73: The get method builds and populates localheaders
(HttpHeaders localheaders) and calls
RestTemplateUtil.getJwttokenFromHeaders(localheaders) but then constructs the
HttpEntity using the wrong variable (headers), so the JWT never gets sent;
change the HttpEntity construction in get(String uri) to use the populated
localheaders (i.e., new HttpEntity<String>(localheaders)) so the token is
included, and remove or reconcile the unused headers variable if necessary to
avoid confusion.

In @src/main/java/com/iemr/tm/utils/JwtUserIdValidationFilter.java:
- Around line 39-64: OPTIONS preflight requests that pass origin checks still
fall through to JWT validation causing 401s; also the full Authorization header
is being logged. In JwtUserIdValidationFilter, inside the OPTIONS branch after
the isOriginAllowed(origin) check add an immediate successful response and
return (e.g., set status SC_OK and return) so valid preflights do not continue
to JWT logic or call chain.doFilter; and replace any logging of the raw
authorization header (authorizationHeader) with a redacted/masked version (e.g.,
log only the scheme or a fixed placeholder) to avoid leaking tokens.
🟠 Major comments (21)
src/main/java/com/iemr/tm/controller/login/IemrMmuLoginController.java-131-145 (1)

131-145: Log injection vulnerability and inconsistent error codes.

  1. Log injection (line 132): Logging user-controlled comingRequest directly can enable log injection attacks (e.g., inserting fake log entries or CRLF injection). Sanitize or avoid logging raw request data.

  2. Inconsistent error code (line 143): Uses 400 here but 5000 is used in getUserServicePointVanDetails (line 91) for similar errors. Standardize error codes for consistency.

🔒 Suggested fixes
         JSONObject obj = new JSONObject(comingRequest);
-        logger.info("getUserVanSpDetails request {}", comingRequest);
+        logger.info("getUserVanSpDetails request received for providerServiceMapID: {}", 
+                    obj.has("providerServiceMapID") ? obj.getInt("providerServiceMapID") : "missing");

         if (obj.has("providerServiceMapID")) {
             String responseData = iemrMmuLoginServiceImpl.getUserVanSpDetails(userID, obj.getInt("providerServiceMapID"));

             response.setResponse(responseData);
         } else {
             response.setError(400, "Invalid request");
         }

     } catch (Exception e) {
-        response.setError(400, "Error while getting van and service points data");
+        response.setError(5000, "Error while getting van and service points data");
         logger.error("getUserVanSpDetails failed", e);
     }
src/main/java/com/iemr/tm/controller/login/IemrMmuLoginController.java-78-87 (1)

78-87: Dead code and redundant null check.

  1. JSONObject obj (line 81) is created but never used after the refactor.
  2. The null check for userID (lines 83-86) is redundant—Integer.valueOf() will throw NumberFormatException if parsing fails, but won't return null on success.

Consider catching NumberFormatException if the principal format is uncertain.

♻️ Proposed fix
         Integer userID = Integer.valueOf(authentication.getPrincipal().toString());
 		
 
-			JSONObject obj = new JSONObject(comingRequest);
 			logger.info("getUserServicePointVanDetails request " + comingRequest);
-			if (userID == null) {
-				response.setError(403, "Unauthorized access: Missing or invalid token");
-				return response.toString();
-			}
 			String responseData = iemrMmuLoginServiceImpl.getUserServicePointVanDetails(userID);
src/main/java/com/iemr/tm/controller/common/main/WorklistController.java-727-728 (1)

727-728: Remove unreachable code.

The condition else if(userID == null) is unreachable because if authentication is null or invalid, the method already returns at lines 715-718. After line 720, userID cannot be null as Integer.valueOf() would throw an exception rather than return null.

♻️ Proposed fix
 			if (providerServiceMapID != null && userID != null ) {
 				String s = commonDoctorServiceImpl.getTCSpecialistWorkListNewForTM(providerServiceMapID, userID,
 						serviceID);
 				if (s != null)
 					response.setResponse(s);
-			} else if(userID  == null ) {
-				response.setError(403, "Unauthorized access!");
 			} else {
 				logger.error("Invalid request");
 				response.setError(5000, "Invalid request");
src/main/java/com/iemr/tm/controller/common/main/WorklistController.java-755-755 (1)

755-755: Add exception handling for user ID parsing.

Converting the authentication principal to an Integer without validation could throw a NumberFormatException if the principal contains non-numeric data.

🐛 Proposed fix
-        Integer userID = Integer.valueOf(authentication.getPrincipal().toString());
+        Integer userID;
+        try {
+            userID = Integer.valueOf(authentication.getPrincipal().toString());
+        } catch (NumberFormatException e) {
+            logger.error("Invalid user ID in authentication principal: " + e);
+            response.setError(403, "Unauthorized access");
+            return response.toString();
+        }
src/main/java/com/iemr/tm/controller/common/main/WorklistController.java-710-711 (1)

710-711: Add @PreAuthorize annotation for role-based access control.

This endpoint is missing the @PreAuthorize annotation, which is inconsistent with the security hardening pattern applied throughout this PR. Based on the method name and similar endpoints (e.g., lines 108, 155, 176), this should be restricted to TC_SPECIALIST or TCSPECIALIST roles.

♻️ Proposed fix
 	@Operation(summary = "Get teleconsultation specialist worklist")
+	@PreAuthorize("hasRole('TC_SPECIALIST') || hasRole('TCSPECIALIST')")
 	@GetMapping(value = { "/getTCSpecialistWorklist/{providerServiceMapID}/{serviceID}" })

Committable suggestion skipped: line range outside the PR's diff.

src/main/java/com/iemr/tm/controller/common/main/WorklistController.java-788-788 (1)

788-788: Add exception handling for user ID parsing.

Converting the authentication principal to an Integer without validation could throw a NumberFormatException if the principal contains non-numeric data.

🐛 Proposed fix
-        Integer userID = Integer.valueOf(authentication.getPrincipal().toString());
+        Integer userID;
+        try {
+            userID = Integer.valueOf(authentication.getPrincipal().toString());
+        } catch (NumberFormatException e) {
+            logger.error("Invalid user ID in authentication principal: " + e);
+            response.setError(403, "Unauthorized access");
+            return response.toString();
+        }
src/main/java/com/iemr/tm/controller/common/main/WorklistController.java-743-744 (1)

743-744: Add @PreAuthorize annotation for role-based access control.

This endpoint is missing the @PreAuthorize annotation, which is inconsistent with the security hardening pattern applied throughout this PR. This should be restricted to TC_SPECIALIST or TCSPECIALIST roles.

♻️ Proposed fix
 	@Operation(summary = "Get teleconsultation specialist worklist for patient app")
+	@PreAuthorize("hasRole('TC_SPECIALIST') || hasRole('TCSPECIALIST')")
 	@GetMapping(value = {

Committable suggestion skipped: line range outside the PR's diff.

src/main/java/com/iemr/tm/controller/common/main/WorklistController.java-775-776 (1)

775-776: Add @PreAuthorize annotation for role-based access control.

This endpoint is missing the @PreAuthorize annotation, which is inconsistent with the security hardening pattern applied throughout this PR. This should be restricted to TC_SPECIALIST or TCSPECIALIST roles.

♻️ Proposed fix
 	@Operation(summary = "Get teleconsultation specialist future scheduled")
+	@PreAuthorize("hasRole('TC_SPECIALIST') || hasRole('TCSPECIALIST')")
 	@GetMapping(value = {

Committable suggestion skipped: line range outside the PR's diff.

src/main/java/com/iemr/tm/controller/common/main/WorklistController.java-720-720 (1)

720-720: Add exception handling for user ID parsing.

Converting the authentication principal to an Integer without validation could throw a NumberFormatException if the principal contains non-numeric data.

🐛 Proposed fix
-        Integer userID = Integer.valueOf(authentication.getPrincipal().toString());
+        Integer userID;
+        try {
+            userID = Integer.valueOf(authentication.getPrincipal().toString());
+        } catch (NumberFormatException e) {
+            logger.error("Invalid user ID in authentication principal: " + e);
+            response.setError(403, "Unauthorized access");
+            return response.toString();
+        }

Committable suggestion skipped: line range outside the PR's diff.

src/main/java/com/iemr/tm/controller/foetalmonitor/FoetalMonitorController.java-31-31 (1)

31-31: Add @PreAuthorize annotations to FoetalMonitorController endpoints.

The PreAuthorize import was added but no security annotation is applied to the controller or its methods. All endpoints remain unprotected, which is inconsistent with the security controls applied to similar medical controllers (AntenatalCareController, PostnatalCareController, GeneralOPDController, NCDCareController, etc.).

Apply method-level or class-level @PreAuthorize annotation to restrict access to appropriate roles (e.g., hasRole('NURSE') || hasRole('DOCTOR')).

src/main/java/com/iemr/tm/utils/JwtUtil.java-70-76 (1)

70-76: Document null return behavior and add validation.

The method returns null when the token is invalid or the userId claim is missing, but this isn't documented. Callers must handle null to avoid NPE.

📝 Proposed improvement
+/**
+ * Extracts the userId from the JWT token.
+ * 
+ * @param token the JWT token
+ * @return the userId as a String, or null if the token is invalid or userId claim is missing
+ */
 public String getUserIdFromToken(String token) {
      Claims claims = validateToken(token);
      if (claims == null) {
          return null;
      }
      return claims.get("userId", String.class);
  }
src/main/java/com/iemr/tm/service/ncdscreening/NCDSCreeningDoctorServiceImpl.java-81-82 (1)

81-82: Logic error: Checking key presence instead of value.

The current code checks if the doctorSignatureFlag key exists (requestOBJ.has("doctorSignatureFlag")), not whether its value is true. If the request contains {"doctorSignatureFlag": false}, doctorSignature will still be true.

Additionally, lines 99-102 are redundant since doctorSignatureFlag is assigned doctorSignature which is already a boolean.

🐛 Proposed fix
-		boolean doctorSignature = requestOBJ.has("doctorSignatureFlag");
-
+		boolean doctorSignatureFlag = false;
+		if (requestOBJ.has("doctorSignatureFlag") && !requestOBJ.get("doctorSignatureFlag").isJsonNull()) {
+			doctorSignatureFlag = requestOBJ.get("doctorSignatureFlag").getAsBoolean();
+		}

 		if (requestOBJ != null) {
 			// ... existing code ...

-			Boolean doctorSignatureFlag = false;
-			if (doctorSignature) {
-			doctorSignatureFlag = doctorSignature;
-			}
-
 			// checking if test is prescribed

Also applies to: 99-102

src/main/java/com/iemr/tm/utils/exception/CustomAuthenticationEntryPoint.java-19-21 (1)

19-21: Potential JSON injection via unescaped exception message.

The exception message is concatenated directly into the JSON string without escaping. If authException.getMessage() contains quotes or special characters, it will produce malformed JSON or could allow injection.

Consider using ObjectMapper for consistent and safe JSON serialization, as done in CustomAccessDeniedHandler.

🔒 Proposed fix
 package com.iemr.tm.utils.exception;
 
 import java.io.IOException;
+import java.util.Map;
 
+import com.fasterxml.jackson.databind.ObjectMapper;
 import org.springframework.security.core.AuthenticationException;
 import org.springframework.security.web.AuthenticationEntryPoint;
 import org.springframework.stereotype.Component;
@@ -12,10 +14,13 @@
 @Component
 public class CustomAuthenticationEntryPoint implements AuthenticationEntryPoint {
 
+    private static final ObjectMapper mapper = new ObjectMapper();
+
     @Override
     public void commence(HttpServletRequest request,
                          HttpServletResponse response,
                          AuthenticationException authException) throws IOException {
         response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); // 401
         response.setContentType("application/json");
-        response.getWriter().write("{\"error\": \"Unauthorized\", \"message\": \"" + authException.getMessage() + "\"}");
+        Map<String, String> errorResponse = Map.of("error", "Unauthorized", "message", authException.getMessage());
+        response.getWriter().write(mapper.writeValueAsString(errorResponse));
     }
 }
src/main/java/com/iemr/tm/repo/login/UserLoginRepo.java-18-19 (1)

18-19: Fix method naming convention and add deleted status filtering.

  1. Method name getRoleNamebyUserId violates Java camelCase convention; rename to getRoleNameByUserId.

  2. The query does not filter on deleted = false. The codebase consistently filters deleted records across all repositories; apply the same pattern here to exclude deleted role mappings:

Suggested fix
@Query(nativeQuery = true, value = "select rolename from m_role where roleid in (select roleid from m_userservicerolemapping where userid=:userID and deleted=false) and deleted=false")
List<String> getRoleNameByUserId(@Param("userID") Long userID);
src/main/java/com/iemr/tm/service/anc/ANCServiceImpl.java-351-356 (1)

351-356: Align ANC doctor save flow doctorSignatureFlag with JSON value

saveANCDoctorData currently derives the flag as:

  • boolean doctorSignature = requestOBJ.has("doctorSignatureFlag");
  • doctorSignatureFlag is then set to doctorSignature.

So any request that merely includes "doctorSignatureFlag" will be treated as true, even if the value is false. Also, .has() is called before checking requestOBJ for null, which slightly weakens robustness.

Since updateANCDoctorData already reads the actual boolean value from JSON, the save path should mirror that behavior:

Proposed fix
-        Long referSaveSuccessFlag = null;
-        boolean doctorSignature = requestOBJ.has("doctorSignatureFlag");
-
-        Boolean doctorSignatureFlag = false;
-        if (doctorSignature) {
-            doctorSignatureFlag = doctorSignature;
-        }
+        Long referSaveSuccessFlag = null;
+
+        Boolean doctorSignatureFlag = false;
+        if (requestOBJ != null
+                && requestOBJ.has("doctorSignatureFlag")
+                && !requestOBJ.get("doctorSignatureFlag").isJsonNull()) {
+            doctorSignatureFlag = requestOBJ.get("doctorSignatureFlag").getAsBoolean();
+        }
@@
-                int i = commonDoctorServiceImpl.updateBenFlowtableAfterDocDataSave(commonUtilityClass, isTestPrescribed,
-                        isMedicinePrescribed, tcRequestOBJ,doctorSignatureFlag);
+                int i = commonDoctorServiceImpl.updateBenFlowtableAfterDocDataSave(
+                        commonUtilityClass, isTestPrescribed, isMedicinePrescribed, tcRequestOBJ, doctorSignatureFlag);

This keeps the doctor-signature semantics consistent across ANC modules and prevents incorrectly marking encounters as signed.

Also applies to: 473-475

src/main/java/com/iemr/tm/service/cancerScreening/CSServiceImpl.java-813-819 (1)

813-819: Correct doctorSignatureFlag handling in cancer screening doctor save flow

In saveCancerScreeningDoctorData the flag is derived as:

  • boolean doctorSignature = requestOBJ.has("doctorSignatureFlag");
  • doctorSignatureFlag is set to doctorSignature.

This means:

  • Any request that includes "doctorSignatureFlag" in JSON will set the internal flag to true even if the payload value is false.
  • The .has() call is performed before a null check on requestOBJ, introducing a new NPE risk.

Since this flag is persisted via updateBenFlowAfterDocDataFromSpecialist / updateBenFlowAfterDocData, this can incorrectly mark records as signed.

A safer, value-aware version:

Proposed fix
-        Long docDataSuccessFlag = null;
-        Long tcRequestStatusFlag = null;
-        boolean doctorSignature = requestOBJ.has("doctorSignatureFlag");
-
-        Boolean doctorSignatureFlag = false;
-            if (doctorSignature) {
-            doctorSignatureFlag = doctorSignature;
-            }
+        Long docDataSuccessFlag = null;
+        Long tcRequestStatusFlag = null;
+
+        Boolean doctorSignatureFlag = false;
+        if (requestOBJ != null
+                && requestOBJ.has("doctorSignatureFlag")
+                && !requestOBJ.get("doctorSignatureFlag").isJsonNull()) {
+            doctorSignatureFlag = requestOBJ.get("doctorSignatureFlag").getAsBoolean();
+        }
@@
-                    l1 = commonBenStatusFlowServiceImpl.updateBenFlowAfterDocDataFromSpecialist(tmpBenFlowID,
-                            tmpbeneficiaryRegID, tmpBeneficiaryID, tmpBenVisitID, docFlag, pharmaFalg, oncologistFlag,
-                            tcSpecialistFlag, (short) 0, doctorSignatureFlag);
+                    l1 = commonBenStatusFlowServiceImpl.updateBenFlowAfterDocDataFromSpecialist(
+                            tmpBenFlowID, tmpbeneficiaryRegID, tmpBeneficiaryID, tmpBenVisitID,
+                            docFlag, pharmaFalg, oncologistFlag, tcSpecialistFlag, (short) 0, doctorSignatureFlag);
@@
-                    l2 = commonBenStatusFlowServiceImpl.updateBenFlowAfterDocData(tmpBenFlowID, tmpbeneficiaryRegID,
-                            tmpBeneficiaryID, tmpBenVisitID, docFlag, pharmaFalg, oncologistFlag, tcSpecialistFlag,
-                            tcUserID, tcDate, (short) 0, doctorSignatureFlag);
+                    l2 = commonBenStatusFlowServiceImpl.updateBenFlowAfterDocData(
+                            tmpBenFlowID, tmpbeneficiaryRegID, tmpBeneficiaryID, tmpBenVisitID,
+                            docFlag, pharmaFalg, oncologistFlag, tcSpecialistFlag,
+                            tcUserID, tcDate, (short) 0, doctorSignatureFlag);

Also applies to: 916-928

src/main/java/com/iemr/tm/service/quickConsultation/QuickConsultationServiceImpl.java-319-320 (1)

319-320: Inconsistent doctorSignatureFlag handling causes incorrect value preservation in save path

The save method uses field presence to determine the flag:

boolean doctorSignature = quickConsultDoctorOBJ.has("doctorSignatureFlag");
...
Boolean doctorSignatureFlag = false;
if (doctorSignature) {
    doctorSignatureFlag = doctorSignature;
}

This treats the flag as true whenever the field is present in the JSON, regardless of its actual boolean value. The update method correctly reads the actual boolean value:

Boolean doctorSignatureFlag = false;
if (quickConsultDoctorOBJ.has("doctorSignatureFlag") && !quickConsultDoctorOBJ.get("doctorSignatureFlag").isJsonNull()) {
    doctorSignatureFlag = quickConsultDoctorOBJ.get("doctorSignatureFlag").getAsBoolean();
}

If a client sends "doctorSignatureFlag": false, the save path will incorrectly set it to true while the update path will correctly set it to false. This causes inconsistent behavior between the two operations and silently corrupts data.

Align the save path logic to match the update path by reading the actual boolean value instead of checking field presence.

src/main/java/com/iemr/tm/utils/JwtUserIdValidationFilter.java-132-137 (1)

132-137: Avoid logging raw Authorization/JWT header values

Line 134 logs the full authorization header:

logger.info("TM-API_AUTH:" + authHeader);

If authHeader contains JWTs or other credentials, this leaks secrets into logs, which is a security and compliance risk (log access, backups, etc.).

Additionally, the same vulnerability exists in HTTPRequestInterceptor.java (lines 70 and 127), where the Authorization header is logged unmasked in debug statements.

Prefer logging only the fact that the header is present (or at most a short, non‑identifying prefix). Example fix for line 134:

Proposed fix
-                        logger.info("TM-API_AUTH:"+authHeader);
+                        logger.info("TM-API_AUTH header present for mobile client");

Update the debug logging in HTTPRequestInterceptor.java to mask or omit the Authorization header value.

src/main/java/com/iemr/tm/service/generalOPD/GeneralOPDServiceImpl.java-741-742 (1)

741-742: GeneralOPD doctorSignatureFlag: save path ignores actual JSON boolean value

In saveDoctorData (lines 741-763):

boolean doctorSignature = requestOBJ.has("doctorSignatureFlag");
...
Boolean doctorSignatureFlag = false;
if (doctorSignature) {
    doctorSignatureFlag = doctorSignature;
}

This uses only the presence of the field; "doctorSignatureFlag": false will propagate as true. The update method correctly reads the actual value (lines 1370-1373):

Boolean doctorSignatureFlag = false;
if (requestOBJ.has("doctorSignatureFlag") && !requestOBJ.get("doctorSignatureFlag").isJsonNull()) {
    doctorSignatureFlag = requestOBJ.get("doctorSignatureFlag").getAsBoolean();
}

Align the save path with the update path to prevent behavioral discrepancies between create and update flows.

src/main/java/com/iemr/tm/service/ncdscreening/NCDScreeningServiceImpl.java-1011-1011 (1)

1011-1011: doctorSignatureFlag must read the actual JSON boolean value, not field presence

Line 1011 sets doctorSignature from requestOBJ.has("doctorSignatureFlag"), which only checks field presence. This means any request including the field (even with "doctorSignatureFlag": false) is treated as true. Lines 1027–1031 then copy this incorrectly-determined boolean, and line 1128 passes it to updateBenFlowtableAfterDocDataSave, misrepresenting the doctor's signature status.

Other modules (QuickConsultation, PNC, NCDCare, GeneralOPD, Covid19) correctly handle this by reading the actual boolean value with getAsBoolean() after checking both presence and null status. Apply the same pattern here:

Proposed fix
-        boolean doctorSignature = requestOBJ.has("doctorSignatureFlag");
+        // Read the actual boolean value of doctorSignatureFlag from the request
@@
-            Boolean doctorSignatureFlag = false;
-            if (doctorSignature) {
-            doctorSignatureFlag = doctorSignature;
-            }
+            Boolean doctorSignatureFlag = false;
+            if (requestOBJ.has("doctorSignatureFlag")
+                    && !requestOBJ.get("doctorSignatureFlag").isJsonNull()) {
+                doctorSignatureFlag = requestOBJ.get("doctorSignatureFlag").getAsBoolean();
+            }

Also applies to: 1027–1031, 1128–1129

src/main/java/com/iemr/tm/service/ncdCare/NCDCareServiceImpl.java-759-760 (1)

759-760: NCDCareServiceImpl saveDoctorData: align presence-based check with value-based logic in update path

The save method checks only for field presence while the update method correctly reads the boolean value. When "doctorSignatureFlag": false is sent, the save path treats it as true (field exists), but the update path correctly interprets it as false.

In saveDoctorData (line 759), replace:

boolean doctorSignature = requestOBJ.has("doctorSignatureFlag");
// ...
if (doctorSignature) {
    doctorSignatureFlag = doctorSignature;
}

With:

if (requestOBJ.has("doctorSignatureFlag") && !requestOBJ.get("doctorSignatureFlag").isJsonNull()) {
    doctorSignatureFlag = requestOBJ.get("doctorSignatureFlag").getAsBoolean();
}

This matches the update path logic (line 1214–1217) and correctly handles both true and false values.

🟡 Minor comments (2)
src/main/java/com/iemr/tm/controller/patientApp/master/PatientAppCommonMasterController.java-135-135 (1)

135-135: Both TCSPECIALIST and TC_SPECIALIST role checks are intentional; verify role data consistency in the database.

The role transformation logic in RoleAuthenticationFilter normalizes role names by converting spaces to underscores: "ROLE_" + r.toUpperCase().replace(" ", "_"). This means the system is designed to handle both role variants from the database.

The dual checks across six endpoints (lines 135, 157, 179, 201, 223, 245) and other controllers indicate this is intentional to support inconsistent role naming—either from incomplete migration or inconsistent role assignment in the database.

Confirm:

  1. Whether both role variants actually exist in user role data or if one should be standardized
  2. If this is legacy support that can be cleaned up
src/main/java/com/iemr/tm/utils/JwtAuthenticationUtil.java-138-146 (1)

138-146: Fix exception handling and typo in error message.

  1. The catch (Exception e) block catches the IEMRException thrown at line 141, causing it to be re-wrapped with a different message.

  2. Typo at line 145: "Failed to retrieverole for usedId" should be "Failed to retrieve role for userId".

🐛 Proposed fix
 	public List<String> getUserRoles(Long userId) throws IEMRException {
 		if (null == userId || userId <= 0) {
 			throw new IEMRException("Invalid User ID : " + userId);
 		}
-		try {
-			List<String> role = userLoginRepo.getRoleNamebyUserId(userId);
-			if (null == role || role.isEmpty()) {
-				throw new IEMRException("No role found for userId : " + userId);
-			}
-			return role;
-		} catch (Exception e) {
-			throw new IEMRException("Failed to retrieverole for usedId : " + userId + " error : " + e.getMessage());
+		List<String> role = userLoginRepo.getRoleNamebyUserId(userId);
+		if (null == role || role.isEmpty()) {
+			throw new IEMRException("No role found for userId : " + userId);
 		}
+		return role;
 	}

Committable suggestion skipped: line range outside the PR's diff.

🧹 Nitpick comments (25)
src/main/java/com/iemr/tm/controller/location/LocationController.java (1)

45-45: Use hasAnyRole() for improved readability.

The current annotation works but is verbose. Spring Security's hasAnyRole() provides a cleaner alternative. The duplicate role naming (e.g., LABTECHNICIAN/LAB_TECHNICIAN, TCSPECIALIST/TC_SPECIALIST, DATASYNC/DATA_SYNC) is used consistently throughout the codebase for backward compatibility with existing role assignments and should be preserved.

♻️ Suggested refactor using hasAnyRole()
-@PreAuthorize("hasRole('NURSE') || hasRole('PHARMACIST') || hasRole('LABTECHNICIAN') || hasRole('REGISTRAR') || hasRole('DATASYNC') || hasRole('DATA_SYNC') || hasRole('DOCTOR') || hasRole('LAB_TECHNICIAN') || hasRole('TC_SPECIALIST') || hasRole('TCSPECIALIST') || hasRole('ONCOLOGIST') || hasRole('RADIOLOGIST')")
+@PreAuthorize("hasAnyRole('NURSE', 'PHARMACIST', 'LABTECHNICIAN', 'REGISTRAR', 'DATASYNC', 'DATA_SYNC', 'DOCTOR', 'LAB_TECHNICIAN', 'TC_SPECIALIST', 'TCSPECIALIST', 'ONCOLOGIST', 'RADIOLOGIST')")
src/main/java/com/iemr/tm/controller/login/IemrMmuLoginController.java (2)

57-59: Unused JwtUtil dependency.

jwtUtil is autowired but never used in this controller. The user ID is extracted directly from authentication.getPrincipal().toString(). Remove this unused field to keep the code clean.

🧹 Suggested removal
-
-	@Autowired
-	private JwtUtil jwtUtil;
-	

49-50: Standardize role naming conventions.

The @PreAuthorize annotation includes both LABTECHNICIAN and LAB_TECHNICIAN, as well as DATASYNC and DATA_SYNC. This inconsistency extends across the codebase (e.g., LocationController.java, RegistrarController.java, WorklistController.java) and also affects TCSPECIALIST vs TC_SPECIALIST. Standardizing role names across the application will improve maintainability and reduce the risk of access control gaps.

src/main/java/com/iemr/tm/controller/common/main/WorklistController.java (1)

62-64: Remove unused JwtUtil dependency.

The jwtUtil field is autowired but never used in this controller. Based on the relevant code snippets, other controllers (e.g., IemrMmuLoginController, VideoConsultationController) use jwtUtil to extract user information from JWT tokens, but this controller extracts the userID directly from the Authentication object's principal.

♻️ Proposed fix
-	@Autowired
-	private JwtUtil jwtUtil;
-
src/main/java/com/iemr/tm/utils/http/HTTPRequestInterceptor.java (1)

149-163: Consider caching parsed origin patterns for performance.

The method splits and converts allowedOrigins patterns to regex on every call. Since this runs on each error response, consider pre-compiling and caching the patterns during initialization for better performance.

♻️ Suggested optimization with pattern caching

Add a field to cache compiled patterns:

private List<Pattern> compiledOriginPatterns;

@PostConstruct
public void initOriginPatterns() {
    if (allowedOrigins != null && !allowedOrigins.trim().isEmpty()) {
        compiledOriginPatterns = Arrays.stream(allowedOrigins.split(","))
            .map(String::trim)
            .map(pattern -> {
                String regex = pattern
                    .replace(".", "\\.")
                    .replace("*", ".*")
                    .replace("http://localhost:.*", "http://localhost:\\d+");
                return Pattern.compile(regex);
            })
            .collect(Collectors.toList());
    }
}

private boolean isOriginAllowed(String origin) {
    if (origin == null || compiledOriginPatterns == null || compiledOriginPatterns.isEmpty()) {
        return false;
    }
    return compiledOriginPatterns.stream()
        .anyMatch(pattern -> pattern.matcher(origin).matches());
}

Don't forget to add imports:

import java.util.List;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import jakarta.annotation.PostConstruct;
src/main/java/com/iemr/tm/controller/patientApp/master/PatientAppCommonMasterController.java (1)

65-65: Authorization looks good with minor style note.

The role-based access restrictions for NURSE and DOCTOR are appropriate for these medical data operations. Note that there are trailing spaces in the annotation strings (e.g., "hasRole('NURSE') || hasRole('DOCTOR') "), which don't affect functionality but could be cleaned up for consistency.

Also applies to: 80-80, 95-95, 115-115

src/main/java/com/iemr/tm/controller/labtechnician/LabtechnicianController.java (1)

50-50: Consider removing trailing space in the annotation.

The @PreAuthorize annotation has a trailing space within the string. While this doesn't affect functionality, removing it improves consistency with string literals elsewhere in the codebase.

♻️ Proposed fix
-@PreAuthorize("hasRole('LAB_TECHNICIAN') || hasRole('LABTECHNICIAN') ")
+@PreAuthorize("hasRole('LAB_TECHNICIAN') || hasRole('LABTECHNICIAN')")
src/main/java/com/iemr/tm/controller/nurse/vitals/AnthropometryVitalsController.java (1)

22-22: Consider removing trailing space in the annotation.

The @PreAuthorize annotation contains a trailing space. While functionally harmless, removing it improves string consistency across the codebase.

♻️ Proposed fix
-@PreAuthorize("hasRole('NURSE') ")
+@PreAuthorize("hasRole('NURSE')")
src/main/java/com/iemr/tm/controller/ncdCare/NCDCareController.java (1)

73-73: Consider removing trailing spaces in annotations.

Several @PreAuthorize annotations contain trailing spaces within their string values. While this doesn't affect functionality, removing them improves consistency.

♻️ Proposed fix
-@PreAuthorize("hasRole('NURSE') ")
+@PreAuthorize("hasRole('NURSE')")

-@PreAuthorize("hasRole('DOCTOR') ")
+@PreAuthorize("hasRole('DOCTOR')")

-@PreAuthorize("hasRole('NURSE') || hasRole('DOCTOR') ")
+@PreAuthorize("hasRole('NURSE') || hasRole('DOCTOR')")

Also applies to: 110-110, 143-143, 175-175, 206-206, 239-239, 266-266, 304-304, 339-339

src/main/java/com/iemr/tm/controller/quickconsult/QuickConsultController.java (1)

28-28: Security annotations properly applied to quick consultation endpoints.

The role-based access control correctly restricts nurse-only, doctor-only, and shared endpoints with appropriate @PreAuthorize annotations.

Minor formatting inconsistency: some @PreAuthorize annotation strings contain trailing whitespace (particularly double spaces on lines 116, 155, 186, 219, and 246). While this doesn't affect functionality, cleaning these up would improve code consistency.

♻️ Optional cleanup example
-@PreAuthorize("hasRole('DOCTOR')  ")
+@PreAuthorize("hasRole('DOCTOR')")

Also applies to: 79-79, 116-116, 155-155, 186-186, 219-219, 246-246

src/main/java/com/iemr/tm/utils/redis/RedisStorage.java (1)

99-121: Role caching helpers are fine; consider null/empty handling and richer logging

The cache/get helpers look correct for a best-effort cache, but two small improvements would make them more robust:

  • Guard against roles == null or roles.isEmpty() before rightPushAll to avoid surprising NPEs from the driver.
  • When catching Exception, consider logging the exception object (logger.warn("Failed to cache role for user {}", userId, e);) so stack traces are available during production debugging.
src/main/java/com/iemr/tm/controller/videoconsultationcontroller/VideoConsultationController.java (1)

53-75: Authentication check is good; principal→userID assumption is brittle

Using Authentication to gate /login/{userID} is a good step, but this line is risky:

String userId = authentication.getPrincipal().toString();

It assumes the principal’s toString() is exactly the numeric userID. If your security setup ever returns a UserDetails/custom principal or a non‑numeric identifier, this comparison will silently fail (or break other places if you cast/parse).

Consider:

  • Using a strongly‑typed principal (e.g., custom UserPrincipal exposing getUserId()), or
  • Using your existing JwtUtil to extract the user id from the token instead of relying on toString().
src/main/java/com/iemr/tm/controller/teleconsultation/TeleConsultationController.java (1)

49-52: Auth-based TC worklist is good; principal→Integer conversion is fragile

Moving getTCSpecialistWorkListNew to use Authentication instead of a userId in the body is a solid improvement, and the class-level @PreAuthorize("hasRole('TCSPECIALIST') || hasRole('TC_SPECIALIST') ") matches that intent.

One concern:

Integer userID = Integer.valueOf(authentication.getPrincipal().toString());

This assumes the principal’s string form is always a numeric user id. If your security config ever switches to a custom principal or a username that isn’t numeric, this will throw NumberFormatException and drop into the generic error handler.

Consider:

  • Using a dedicated principal type with an explicit getUserId(), or
  • Extracting the user id from the JWT via JwtUtil/claims instead of toString().

Also, the subsequent if (userID != null) is redundant, since Integer.valueOf never returns null.

Also applies to: 58-60, 148-178

src/main/java/com/iemr/tm/utils/CookieUtil.java (2)

12-13: Consider removing @service annotation.

The class now contains only static utility methods. The @Service annotation is unnecessary since no instance is needed for dependency injection. Consider converting to a utility class or removing the annotation.

Also applies to: 15-15


34-34: Consider using Constants for cookie names.

Constants.java defines JWT_TOKEN = "Jwttoken", but this class uses hardcoded strings "Jwttoken" (line 34) and "Authorization" (line 44). Consider using the constants for consistency and maintainability.

Also applies to: 44-44

src/main/java/com/iemr/tm/utils/Constants.java (1)

3-8: LGTM - Clean constants utility class.

Good use of private constructor to prevent instantiation. Consider adding an AUTHORIZATION constant for consistency with CookieUtil.getAuthTokenFromCookie() which uses the hardcoded "Authorization" string.

src/main/java/com/iemr/tm/controller/generalOPD/GeneralOPDController.java (1)

28-28: Role mappings look consistent with authority convention; optional readability tweak

The new @PreAuthorize guards align with the ROLE_… authority convention from RoleAuthenticationFilter (e.g., hasRole('NURSE') vs ROLE_NURSE), and the NURSE/DOCTOR combinations match the controller responsibilities. If you want to simplify expressions like "hasRole('NURSE') || hasRole('DOCTOR')" you could switch to hasAnyRole('NURSE','DOCTOR'), but that’s only a readability preference.

Also applies to: 72-75, 109-112, 141-147, 174-178, 205-209, 237-241, 268-273, 296-300, 331-335, 366-370, 401-405, 435-439

src/main/java/com/iemr/tm/utils/mapper/RoleAuthenticationFilter.java (1)

29-145: Filter logic is sound; consider minor DRY/clarity tweaks

The authentication flow (JWT → legacy Redis session → role cache/DB → SecurityContext) looks correct and defensive: on any failure you skip auth and continue the chain while clearing the context on hard errors. Two small, optional improvements:

  • Cache the result of CookieUtil.getJwtTokenFromCookie(request) in a local variable instead of calling it twice.
  • Consider logging at debug for high-frequency events like “UserId resolved from JWT/Redis” to keep info logs quieter in production.

These are non-blocking and won’t change behavior.

src/main/java/com/iemr/tm/utils/RestTemplateUtil.java (2)

53-63: Consolidate JWT cookie extraction to avoid duplication

extractJwttoken mirrors the try/catch already present in createRequestEntity. To reduce drift over time, consider reusing extractJwttoken inside createRequestEntity as well, so any future change to how the JWT is read/logged is made in one place.


65-83: Clarify responsibilities of getJwttokenFromHeaders and avoid surprising header changes

getJwttokenFromHeaders currently:

  • Adds Content-Type and User-Agent, and
  • Adds JWT information (Cookie or header).

If callers only expect JWT enrichment, the implicit Content-Type addition could lead to duplicate or conflicting values if they already set it. You might either:

  • Rename the method to reflect that it standardizes multiple headers (not just JWT), or
  • Restrict it to JWT/User-Agent concerns and leave Content-Type to the caller.

Also, consider using headers.set(...) instead of add(...) for these singleton headers to avoid duplicate entries.

src/main/java/com/iemr/tm/service/pnc/PNCServiceImpl.java (1)

303-304: Make doctorSignatureFlag extraction null-safe and consistent

In savePNCDoctorData, boolean doctorSignature = requestOBJ.has("doctorSignatureFlag"); is evaluated before the if (requestOBJ != null) guard. While callers likely never pass null, this introduces an unnecessary NPE risk and differs from the safer pattern you already use in updatePNCDoctorData.

You can simplify and harden the code by inlining the presence/value check:

-        boolean doctorSignature = requestOBJ.has("doctorSignatureFlag");
@@
-            Boolean doctorSignatureFlag = false;
-            if (doctorSignature) {
-            doctorSignatureFlag = requestOBJ.get("doctorSignatureFlag").getAsBoolean();
-            }
+        Boolean doctorSignatureFlag = false;
+        if (requestOBJ != null
+                && requestOBJ.has("doctorSignatureFlag")
+                && !requestOBJ.get("doctorSignatureFlag").isJsonNull()) {
+            doctorSignatureFlag = requestOBJ.get("doctorSignatureFlag").getAsBoolean();
+        }

The rest of the propagation to updateBenFlowtableAfterDocDataSave/Update looks correct.

Also applies to: 318-321, 423-425, 1389-1393, 1501-1503

src/main/java/com/iemr/tm/utils/JwtUserIdValidationFilter.java (1)

159-166: localhost origin wildcard replacement likely never matches due to replacement order

In isOriginAllowed:

String regex = pattern
        .replace(".", "\\.")
        .replace("*", ".*")
        .replace("http://localhost:.*", "http://localhost:\\d+");

Because . is escaped first, any configured pattern like http://localhost:.* becomes http://localhost:\.* before the final replace("http://localhost:.*", ...) runs, so that last replacement probably never matches. This means any special handling you intended for http://localhost:<port> patterns won’t actually be applied.

Consider:

  • Applying the localhost replacement before escaping dots, or
  • Using a more explicit normalization step (e.g., checking pattern.startsWith("http://localhost:") and replacing just the port part), or
  • Treating localhost as a special case outside this generic pattern transformation.
Example adjustment
-        return Arrays.stream(allowedOrigins.split(","))
-                .map(String::trim)
-                .anyMatch(pattern -> {
-                    String regex = pattern
-                            .replace(".", "\\.")
-                            .replace("*", ".*")
-                            .replace("http://localhost:.*", "http://localhost:\\d+");
+        return Arrays.stream(allowedOrigins.split(","))
+                .map(String::trim)
+                .anyMatch(pattern -> {
+                    if (pattern.startsWith("http://localhost:")) {
+                        // Accept any numeric port on localhost
+                        String regex = "http://localhost:\\\\d+";
+                        return origin.matches(regex);
+                    }
+                    String regex = pattern
+                            .replace(".", "\\.")
+                            .replace("*", ".*");
                     boolean matched = origin.matches(regex);
                     return matched;
                 });

Please confirm the expected format of localhost entries in allowedOrigins and adjust the normalization to match that contract.

src/main/java/com/iemr/tm/utils/mapper/SecurityConfig.java (1)

33-52: Clarify CSRF configuration or remove unused CookieCsrfTokenRepository

CookieCsrfTokenRepository is instantiated and configured, but CSRF is then globally disabled:

CookieCsrfTokenRepository csrfTokenRepository = new CookieCsrfTokenRepository();
csrfTokenRepository.setCookieHttpOnly(true);
csrfTokenRepository.setCookiePath("/");
http
    .csrf(csrf -> csrf.disable())

As written, the repository is never used. That’s harmless but confusing for future readers and might suggest CSRF protection is in place when it is not.

Either wire the repository into the CSRF config (if you want cookie-based CSRF tokens), or drop its creation entirely to reflect that the app is operating in a fully stateless, CSRF-disabled mode:

Example clean-up
-    CookieCsrfTokenRepository csrfTokenRepository = new CookieCsrfTokenRepository();
-    csrfTokenRepository.setCookieHttpOnly(true);
-    csrfTokenRepository.setCookiePath("/");
-    http
-        .csrf(csrf -> csrf.disable())
+    http
+        .csrf(csrf -> csrf.disable())

Please confirm that all clients rely on stateless token auth only and that CSRF protection is intentionally disabled for non-browser flows.

src/main/java/com/iemr/tm/controller/covid19/CovidController.java (1)

47-47: Unusual use of Lettuce's @Param annotation.

The code imports io.lettuce.core.dynamic.annotation.Param which is from the Redis Lettuce client, but it's being used on controller method parameters. This annotation has no effect in Spring MVC controllers. If parameter binding is needed, use Spring's @RequestParam or rely on the existing @RequestBody annotations already in place.

♻️ Suggested fix
-import io.lettuce.core.dynamic.annotation.Param;

Remove the @Param annotations from method parameters like getBenVisitDetailsFrmNurseCovid19, getBenCovid19HistoryDetails, etc., as they serve no purpose in this context.

src/main/java/com/iemr/tm/controller/ncdscreening/NCDScreeningController.java (1)

48-48: Same issue: Lettuce's @Param annotation has no effect here.

This is the same issue as in CovidController.java. The io.lettuce.core.dynamic.annotation.Param import is from the Redis Lettuce client and has no effect in Spring MVC controllers.

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between dc84bd9 and 5901e3d.

📒 Files selected for processing (54)
  • pom.xml
  • src/main/java/com/iemr/tm/controller/anc/AntenatalCareController.java
  • src/main/java/com/iemr/tm/controller/cancerscreening/CancerScreeningController.java
  • src/main/java/com/iemr/tm/controller/common/main/WorklistController.java
  • src/main/java/com/iemr/tm/controller/common/master/CommonMasterController.java
  • src/main/java/com/iemr/tm/controller/covid19/CovidController.java
  • src/main/java/com/iemr/tm/controller/dataSyncActivity/StartSyncActivity.java
  • src/main/java/com/iemr/tm/controller/dataSyncLayerCentral/MMUDataSyncVanToServer.java
  • src/main/java/com/iemr/tm/controller/foetalmonitor/FoetalMonitorController.java
  • src/main/java/com/iemr/tm/controller/generalOPD/GeneralOPDController.java
  • src/main/java/com/iemr/tm/controller/labtechnician/LabtechnicianController.java
  • src/main/java/com/iemr/tm/controller/location/LocationController.java
  • src/main/java/com/iemr/tm/controller/login/IemrMmuLoginController.java
  • src/main/java/com/iemr/tm/controller/ncdCare/NCDCareController.java
  • src/main/java/com/iemr/tm/controller/ncdscreening/NCDScreeningController.java
  • src/main/java/com/iemr/tm/controller/nurse/vitals/AnthropometryVitalsController.java
  • src/main/java/com/iemr/tm/controller/patientApp/master/PatientAppCommonMasterController.java
  • src/main/java/com/iemr/tm/controller/pnc/PostnatalCareController.java
  • src/main/java/com/iemr/tm/controller/quickconsult/QuickConsultController.java
  • src/main/java/com/iemr/tm/controller/registrar/main/RegistrarController.java
  • src/main/java/com/iemr/tm/controller/report/CRMReportController.java
  • src/main/java/com/iemr/tm/controller/snomedct/SnomedController.java
  • src/main/java/com/iemr/tm/controller/teleconsultation/TeleConsultationController.java
  • src/main/java/com/iemr/tm/controller/videoconsultationcontroller/VideoConsultationController.java
  • src/main/java/com/iemr/tm/data/benFlowStatus/BeneficiaryFlowStatus.java
  • src/main/java/com/iemr/tm/data/ncdcare/NCDCareDiagnosis.java
  • src/main/java/com/iemr/tm/repo/benFlowStatus/BeneficiaryFlowStatusRepo.java
  • src/main/java/com/iemr/tm/repo/login/UserLoginRepo.java
  • src/main/java/com/iemr/tm/repo/nurse/ncdcare/NCDCareDiagnosisRepo.java
  • src/main/java/com/iemr/tm/service/anc/ANCServiceImpl.java
  • src/main/java/com/iemr/tm/service/benFlowStatus/CommonBenStatusFlowServiceImpl.java
  • src/main/java/com/iemr/tm/service/cancerScreening/CSServiceImpl.java
  • src/main/java/com/iemr/tm/service/common/transaction/CommonDoctorServiceImpl.java
  • src/main/java/com/iemr/tm/service/common/transaction/CommonNurseServiceImpl.java
  • src/main/java/com/iemr/tm/service/covid19/Covid19ServiceImpl.java
  • src/main/java/com/iemr/tm/service/generalOPD/GeneralOPDServiceImpl.java
  • src/main/java/com/iemr/tm/service/ncdCare/NCDCareServiceImpl.java
  • src/main/java/com/iemr/tm/service/ncdscreening/NCDSCreeningDoctorServiceImpl.java
  • src/main/java/com/iemr/tm/service/ncdscreening/NCDScreeningServiceImpl.java
  • src/main/java/com/iemr/tm/service/pnc/PNCServiceImpl.java
  • src/main/java/com/iemr/tm/service/quickConsultation/QuickConsultationServiceImpl.java
  • src/main/java/com/iemr/tm/utils/Constants.java
  • src/main/java/com/iemr/tm/utils/CookieUtil.java
  • src/main/java/com/iemr/tm/utils/JwtAuthenticationUtil.java
  • src/main/java/com/iemr/tm/utils/JwtUserIdValidationFilter.java
  • src/main/java/com/iemr/tm/utils/JwtUtil.java
  • src/main/java/com/iemr/tm/utils/RestTemplateUtil.java
  • src/main/java/com/iemr/tm/utils/exception/CustomAccessDeniedHandler.java
  • src/main/java/com/iemr/tm/utils/exception/CustomAuthenticationEntryPoint.java
  • src/main/java/com/iemr/tm/utils/http/HTTPRequestInterceptor.java
  • src/main/java/com/iemr/tm/utils/http/HttpUtils.java
  • src/main/java/com/iemr/tm/utils/mapper/RoleAuthenticationFilter.java
  • src/main/java/com/iemr/tm/utils/mapper/SecurityConfig.java
  • src/main/java/com/iemr/tm/utils/redis/RedisStorage.java
🧰 Additional context used
🧠 Learnings (3)
📚 Learning: 2024-12-16T11:46:15.606Z
Learnt from: ravishanigarapu
Repo: PSMRI/TM-API PR: 58
File: src/main/java/com/iemr/tm/utils/http/HTTPRequestInterceptor.java:93-97
Timestamp: 2024-12-16T11:46:15.606Z
Learning: In the `HTTPRequestInterceptor` class (`src/main/java/com/iemr/tm/utils/http/HTTPRequestInterceptor.java`), the `validator` is mandatory and always initialized, so null checks are unnecessary.

Applied to files:

  • src/main/java/com/iemr/tm/utils/http/HTTPRequestInterceptor.java
📚 Learning: 2025-04-03T12:46:54.443Z
Learnt from: srishtigrp78
Repo: PSMRI/TM-API PR: 66
File: src/main/java/com/iemr/tm/service/pnc/PNCServiceImpl.java:200-208
Timestamp: 2025-04-03T12:46:54.443Z
Learning: The JSON field name "pNCDeatils" (instead of "pNCDetails") in PNCServiceImpl is intentionally preserved despite the typo to maintain backward compatibility with existing code.

Applied to files:

  • src/main/java/com/iemr/tm/service/pnc/PNCServiceImpl.java
  • src/main/java/com/iemr/tm/data/ncdcare/NCDCareDiagnosis.java
  • src/main/java/com/iemr/tm/repo/nurse/ncdcare/NCDCareDiagnosisRepo.java
📚 Learning: 2025-05-22T09:27:49.422Z
Learnt from: ravishanigarapu
Repo: PSMRI/TM-API PR: 79
File: src/main/java/com/iemr/tm/utils/UserAgentContext.java:3-18
Timestamp: 2025-05-22T09:27:49.422Z
Learning: JwtUserIdValidationFilter.java already includes a try-finally block to properly clear UserAgentContext's ThreadLocal resources after use to prevent memory leaks.

Applied to files:

  • src/main/java/com/iemr/tm/utils/mapper/RoleAuthenticationFilter.java
  • src/main/java/com/iemr/tm/utils/JwtUserIdValidationFilter.java
🧬 Code graph analysis (4)
src/main/java/com/iemr/tm/utils/http/HttpUtils.java (1)
src/main/java/com/iemr/tm/utils/RestTemplateUtil.java (1)
  • RestTemplateUtil (15-84)
src/main/java/com/iemr/tm/utils/exception/CustomAuthenticationEntryPoint.java (5)
src/main/java/com/iemr/tm/utils/mapper/RoleAuthenticationFilter.java (1)
  • Component (29-166)
src/main/java/com/iemr/tm/utils/JwtAuthenticationUtil.java (1)
  • Component (22-148)
src/main/java/com/iemr/tm/utils/JwtUtil.java (1)
  • Component (14-77)
src/main/java/com/iemr/tm/utils/exception/CustomAccessDeniedHandler.java (1)
  • Component (14-28)
src/main/java/com/iemr/tm/utils/redis/RedisStorage.java (1)
  • Component (37-122)
src/main/java/com/iemr/tm/utils/mapper/RoleAuthenticationFilter.java (3)
src/main/java/com/iemr/tm/utils/JwtAuthenticationUtil.java (1)
  • Component (22-148)
src/main/java/com/iemr/tm/utils/JwtUtil.java (1)
  • Component (14-77)
src/main/java/com/iemr/tm/utils/redis/RedisStorage.java (1)
  • Component (37-122)
src/main/java/com/iemr/tm/controller/common/main/WorklistController.java (1)
src/main/java/com/iemr/tm/controller/login/IemrMmuLoginController.java (1)
  • RestController (47-167)
🪛 GitHub Check: SonarCloud
src/main/java/com/iemr/tm/controller/login/IemrMmuLoginController.java

[notice] 132-132: Logging should not be vulnerable to injection attacks

Change this code to not log user-controlled data.

See more on SonarQube Cloud

Comment on lines 63 to 73
public String get(String uri) {
String body;
HttpHeaders localheaders = new HttpHeaders();
localheaders.add("Content-Type", "application/json");
RestTemplateUtil.getJwttokenFromHeaders(localheaders);
HttpEntity<String> requestEntity = new HttpEntity<String>(headers);
ResponseEntity<String> responseEntity = rest.exchange(uri, HttpMethod.GET, requestEntity, String.class);
setStatus((HttpStatus) responseEntity.getStatusCode());
body = responseEntity.getBody();
return body;
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Critical: JWT token not used in the request.

Lines 65-67 create and populate localheaders with JWT token information, but line 68 creates the HttpEntity using the original headers object instead. This means the JWT token will not be included in the HTTP request, breaking authentication token propagation.

🔧 Proposed fix
 public String get(String uri) {
   String body;
   HttpHeaders localheaders = new HttpHeaders();
   localheaders.add("Content-Type", "application/json");
   RestTemplateUtil.getJwttokenFromHeaders(localheaders);
-  HttpEntity<String> requestEntity = new HttpEntity<String>(headers);
+  HttpEntity<String> requestEntity = new HttpEntity<String>(localheaders);
   ResponseEntity<String> responseEntity = rest.exchange(uri, HttpMethod.GET, requestEntity, String.class);
   setStatus((HttpStatus) responseEntity.getStatusCode());
   body = responseEntity.getBody();
   return body;
 }
🤖 Prompt for AI Agents
In @src/main/java/com/iemr/tm/utils/http/HttpUtils.java around lines 63 - 73,
The get method builds and populates localheaders (HttpHeaders localheaders) and
calls RestTemplateUtil.getJwttokenFromHeaders(localheaders) but then constructs
the HttpEntity using the wrong variable (headers), so the JWT never gets sent;
change the HttpEntity construction in get(String uri) to use the populated
localheaders (i.e., new HttpEntity<String>(localheaders)) so the token is
included, and remove or reconcile the unused headers variable if necessary to
avoid confusion.

Comment on lines 39 to +64
String origin = request.getHeader("Origin");
String method = request.getMethod();
String uri = request.getRequestURI();

logger.debug("Incoming Origin: {}", origin);
logger.debug("Allowed Origins Configured: {}", allowedOrigins);
if ("OPTIONS".equalsIgnoreCase(method)) {
if (origin == null) {
logger.warn("BLOCKED - OPTIONS request without Origin header | Method: {} | URI: {}", method, uri);
response.sendError(HttpServletResponse.SC_FORBIDDEN, "OPTIONS request requires Origin header");
return;
}
if (!isOriginAllowed(origin)) {
logger.warn("BLOCKED - Unauthorized Origin | Origin: {} | Method: {} | URI: {}", origin, method, uri);
response.sendError(HttpServletResponse.SC_FORBIDDEN, "Origin not allowed");
return;
}
} else {
// For non-OPTIONS requests, validate origin if present
if (origin != null && !isOriginAllowed(origin)) {
logger.warn("BLOCKED - Unauthorized Origin | Origin: {} | Method: {} | URI: {}", origin, method, uri);
response.sendError(HttpServletResponse.SC_FORBIDDEN, "Origin not allowed");
return;
}
}

Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

OPTIONS preflight requests will fail with 401 due to JWT validation

The origin validation correctly blocks missing or disallowed Origin headers, but for allowed origins and OPTIONS requests, the code does not return early. After the origin check passes (lines 55–57), execution continues to line 67 where chain.doFilter(request, response) is called. After that returns, the JWT validation logic (lines 84–110) runs for all requests, including OPTIONS.

Browser preflight requests have no Authorization header, so they hit line 105 and return 401: "No valid authentication token found". Browsers treat a non–2xx preflight as a network error, blocking all CORS requests to protected endpoints.

Add an early return for valid OPTIONS requests before JWT validation:

Proposed fix
         if ("OPTIONS".equalsIgnoreCase(method)) {
             if (origin == null) {
                 logger.warn("BLOCKED - OPTIONS request without Origin header | Method: {} | URI: {}", method, uri);
                 response.sendError(HttpServletResponse.SC_FORBIDDEN, "OPTIONS request requires Origin header");
                 return;
             }
             if (!isOriginAllowed(origin)) {
                 logger.warn("BLOCKED - Unauthorized Origin | Origin: {} | Method: {} | URI: {}", origin, method, uri);
                 response.sendError(HttpServletResponse.SC_FORBIDDEN, "Origin not allowed");
                 return;
             }
+            // Valid preflight—send CORS headers and return 200
+            response.setHeader("Access-Control-Allow-Origin", origin);
+            response.setHeader("Access-Control-Allow-Methods", "GET, POST, PUT, PATCH, DELETE, OPTIONS");
+            response.setHeader("Access-Control-Allow-Headers", "Authorization, Content-Type, Accept, Jwttoken, serverAuthorization, ServerAuthorization, serverauthorization, Serverauthorization");
+            response.setHeader("Access-Control-Allow-Credentials", "true");
+            response.setHeader("Access-Control-Max-Age", "3600");
+            response.setStatus(HttpServletResponse.SC_OK);
+            return;
         } else {
             // For non-OPTIONS requests, validate origin if present
             if (origin != null && !isOriginAllowed(origin)) {
                 logger.warn("BLOCKED - Unauthorized Origin | Origin: {} | Method: {} | URI: {}", origin, method, uri);
                 response.sendError(HttpServletResponse.SC_FORBIDDEN, "Origin not allowed");
                 return;
             }
         }

Additionally, line 134 logs the full authorization header value, which is a security and compliance risk—redact sensitive tokens from logs.

🤖 Prompt for AI Agents
In @src/main/java/com/iemr/tm/utils/JwtUserIdValidationFilter.java around lines
39 - 64, OPTIONS preflight requests that pass origin checks still fall through
to JWT validation causing 401s; also the full Authorization header is being
logged. In JwtUserIdValidationFilter, inside the OPTIONS branch after the
isOriginAllowed(origin) check add an immediate successful response and return
(e.g., set status SC_OK and return) so valid preflights do not continue to JWT
logic or call chain.doFilter; and replace any logging of the raw authorization
header (authorizationHeader) with a redacted/masked version (e.g., log only the
scheme or a fixed placeholder) to avoid leaking tokens.

@5Amogh 5Amogh requested a review from vishwab1 January 7, 2026 11:56
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants