From ff301e459589aa4cd0c105e3f184f4d78c7f7d83 Mon Sep 17 00:00:00 2001 From: Ravi Shanigarapu Date: Fri, 21 Mar 2025 13:45:30 +0530 Subject: [PATCH 01/14] AMM-1217 --- pom.xml | 6 +-- .../identity/repo/BenFamilyMappingRepo.java | 2 + .../common/identity/repo/BenIdentityRepo.java | 2 + .../common/identity/repo/BenMappingRepo.java | 5 +++ .../identity/repo/BenServiceMappingRepo.java | 4 ++ .../identity/service/IdentityService.java | 40 ++++++++++++++----- 6 files changed, 47 insertions(+), 12 deletions(-) diff --git a/pom.xml b/pom.xml index 9b881e3d..14df17d3 100644 --- a/pom.xml +++ b/pom.xml @@ -26,7 +26,7 @@ 17 jdt_apt 1.2.0.Final - 1.18.22 + 1.18.36 ${ENV_VAR} target/classes/application.properties target/classes/common_${environment}.properties @@ -292,7 +292,7 @@ org.apache.maven.plugins maven-compiler-plugin - 3.12.1 + 3.14.0 ${java.version} ${java.version} @@ -300,7 +300,7 @@ org.projectlombok lombok - ${lombok.version} + ${org.projectlombok.version} org.mapstruct diff --git a/src/main/java/com/iemr/common/identity/repo/BenFamilyMappingRepo.java b/src/main/java/com/iemr/common/identity/repo/BenFamilyMappingRepo.java index f4f55096..f15a8e4d 100644 --- a/src/main/java/com/iemr/common/identity/repo/BenFamilyMappingRepo.java +++ b/src/main/java/com/iemr/common/identity/repo/BenFamilyMappingRepo.java @@ -33,6 +33,7 @@ import org.springframework.transaction.annotation.Transactional; import com.iemr.common.identity.domain.MBeneficiaryfamilymapping; +import com.iemr.common.identity.domain.MBeneficiaryidentity; @Repository public interface BenFamilyMappingRepo extends CrudRepository { @@ -50,5 +51,6 @@ List findByCreatedDateBetweenOrderByBenFamilyMapIdAsc @Modifying @Query(" UPDATE MBeneficiaryfamilymapping set vanSerialNo = :benFamilyMapId WHERE benFamilyMapId = :benFamilyMapId") int updateVanSerialNo(@Param("benFamilyMapId") BigInteger benFamilyMapId); + } diff --git a/src/main/java/com/iemr/common/identity/repo/BenIdentityRepo.java b/src/main/java/com/iemr/common/identity/repo/BenIdentityRepo.java index c3f4bc8a..e20315c3 100644 --- a/src/main/java/com/iemr/common/identity/repo/BenIdentityRepo.java +++ b/src/main/java/com/iemr/common/identity/repo/BenIdentityRepo.java @@ -33,6 +33,7 @@ import org.springframework.transaction.annotation.Transactional; import com.iemr.common.identity.domain.MBeneficiaryidentity; +import com.iemr.common.identity.domain.MBeneficiaryservicemapping; @Repository public interface BenIdentityRepo extends CrudRepository { @@ -88,4 +89,5 @@ List findByBenMapIdAndVanID(@Param("benMapId") BigInteger @Query(value = "select i from MBeneficiaryidentity i where i.identityNo = :identityNo") List searchByIdentityNo(@Param("identityNo") String identityNo); + } diff --git a/src/main/java/com/iemr/common/identity/repo/BenMappingRepo.java b/src/main/java/com/iemr/common/identity/repo/BenMappingRepo.java index fcb3c99d..e3080a5d 100644 --- a/src/main/java/com/iemr/common/identity/repo/BenMappingRepo.java +++ b/src/main/java/com/iemr/common/identity/repo/BenMappingRepo.java @@ -33,6 +33,7 @@ import org.springframework.transaction.annotation.Transactional; import com.iemr.common.identity.domain.MBeneficiarymapping; +import com.iemr.common.identity.domain.MBeneficiaryservicemapping; import com.iemr.common.identity.domain.VBenAdvanceSearch; import com.iemr.common.identity.dto.IdentityDTO; import com.iemr.common.identity.dto.IdentitySearchDTO; @@ -137,4 +138,8 @@ Long getBeneficiaryCountsByVillageIDAndLastModifyDate(@Param("villageIDs") List< +"LEFT JOIN MBeneficiaryservicemapping bsm on bsm.vanSerialNo=bm.benMapId and bm.vanID=bsm.vanID " +"where bm.vanSerialNo=:vanSerialNo and bm.vanID=:vanID") MBeneficiarymapping getMapping(@Param("vanSerialNo") BigInteger vanSerialNo,@Param("vanID") Integer vanID); + + @Query("SELECT a FROM MBeneficiarymapping a WHERE a.vanSerialNo =:vanSerialNo AND a.vanID =:vanID ") + MBeneficiarymapping getWithVanSerialNoVanID(@Param("vanSerialNo") BigInteger vanSerialNo, + @Param("vanID") Integer vanID); } diff --git a/src/main/java/com/iemr/common/identity/repo/BenServiceMappingRepo.java b/src/main/java/com/iemr/common/identity/repo/BenServiceMappingRepo.java index 15801662..49ed72d1 100644 --- a/src/main/java/com/iemr/common/identity/repo/BenServiceMappingRepo.java +++ b/src/main/java/com/iemr/common/identity/repo/BenServiceMappingRepo.java @@ -82,4 +82,8 @@ Long countByProviderServiceMapIdAndRegisteredDateBetween(Integer registeredProvi @Modifying @Query(" UPDATE MBeneficiaryservicemapping set vanSerialNo = :benServiceMapID WHERE benServiceMapID = :benServiceMapID ") int updateVanSerialNo(@Param("benServiceMapID") BigInteger benServiceMapID); + + @Query("SELECT a FROM MBeneficiaryservicemapping a WHERE a.benMapId =:benMapId AND a.vanID =:vanID ") + List getWithVanSerialNoVanID(@Param("benMapId") BigInteger benMapId, + @Param("vanID") Integer vanID); } diff --git a/src/main/java/com/iemr/common/identity/service/IdentityService.java b/src/main/java/com/iemr/common/identity/service/IdentityService.java index 8a0caa83..c123eebb 100644 --- a/src/main/java/com/iemr/common/identity/service/IdentityService.java +++ b/src/main/java/com/iemr/common/identity/service/IdentityService.java @@ -573,12 +573,34 @@ public List searhBeneficiaryByGovIdentity(String identity) } private MBeneficiarymapping getBeneficiariesDTONew(Object[] benMapArr) { - MBeneficiarymapping benMapOBJ = new MBeneficiarymapping(); + MBeneficiarymapping mapping = new MBeneficiarymapping(); if (benMapArr != null && benMapArr.length == 12 && benMapArr[8] != null && benMapArr[9] != null) { - benMapOBJ.setBenMapId(getBigIntegerValueFromObject(benMapArr[0])); - benMapOBJ.setCreatedBy(String.valueOf(benMapArr[10])); - benMapOBJ.setCreatedDate((Timestamp) benMapArr[11]); - benMapOBJ = mappingRepo.getMapping(getBigIntegerValueFromObject(benMapArr[9]), (Integer) benMapArr[8]); + mapping.setBenMapId(getBigIntegerValueFromObject(benMapArr[0])); + mapping.setCreatedBy(String.valueOf(benMapArr[10])); + mapping.setCreatedDate((Timestamp) benMapArr[11]); + mapping = mappingRepo.getWithVanSerialNoVanID(getBigIntegerValueFromObject(benMapArr[9]), (Integer) benMapArr[8]); + MBeneficiaryaddress address = addressRepo.getWithVanSerialNoVanID(getBigIntegerValueFromObject(benMapArr[1]), (Integer) benMapArr[8]); + MBeneficiaryconsent consent = consentRepo.getWithVanSerialNoVanID(getBigIntegerValueFromObject(benMapArr[2]), (Integer) benMapArr[8]); + MBeneficiarycontact contact = contactRepo.getWithVanSerialNoVanID(getBigIntegerValueFromObject(benMapArr[3]), (Integer) benMapArr[8]); + MBeneficiarydetail details = detailRepo.getWith_vanSerialNo_vanID(getBigIntegerValueFromObject(benMapArr[4]), (Integer) benMapArr[8]); + MBeneficiaryregidmapping regidmapping = regIdRepo.getWithVanSerialNoVanID(getBigIntegerValueFromObject(benMapArr[5]), (Integer) benMapArr[8]); + MBeneficiaryAccount account = accountRepo.getWithVanSerialNoVanID(getBigIntegerValueFromObject(benMapArr[7]), (Integer) benMapArr[8]); + MBeneficiaryImage image = imageRepo.getWithVanSerialNoVanID(getBigIntegerValueFromObject(benMapArr[6]), (Integer) benMapArr[8]); + List servicemap = serviceMapRepo.getWithVanSerialNoVanID(getBigIntegerValueFromObject(benMapArr[0]), (Integer) benMapArr[8]); + List identity = identityRepo.findByBenMapIdAndVanID(getBigIntegerValueFromObject(benMapArr[0]), (Integer) benMapArr[8]); + List familymapping = familyMapRepo.findByBenMapIdAndVanIDOrderByBenFamilyMapIdAsc(getBigIntegerValueFromObject(benMapArr[0]), (Integer) benMapArr[8]); + + mapping.setMBeneficiaryaddress(address); + mapping.setMBeneficiaryconsent(consent); + mapping.setMBeneficiarycontact(contact); + mapping.setMBeneficiarydetail(details); + mapping.setMBeneficiaryfamilymappings(familymapping); + mapping.setMBeneficiaryidentities(identity); + mapping.setMBeneficiaryImage(image); + mapping.setMBeneficiaryregidmapping(regidmapping); + mapping.setMBeneficiaryservicemappings(servicemap); + mapping.setMBeneficiaryAccount(account); + //benMapOBJ = mappingRepo.getMapping(getBigIntegerValueFromObject(benMapArr[9]), (Integer) benMapArr[8]); @@ -588,15 +610,15 @@ private MBeneficiarymapping getBeneficiariesDTONew(Object[] benMapArr) { if (obj != null) { if (obj.getHouseoldId() != null) - benMapOBJ.setHouseHoldID(obj.getHouseoldId()); + mapping.setHouseHoldID(obj.getHouseoldId()); if (obj.getGuidelineId() != null) - benMapOBJ.setGuideLineID(obj.getGuidelineId()); + mapping.setGuideLineID(obj.getGuidelineId()); if (obj.getRchid() != null) - benMapOBJ.setRchID(obj.getRchid()); + mapping.setRchID(obj.getRchid()); } } - return benMapOBJ; + return mapping; } private MBeneficiarymapping getBeneficiariesDTONewPartial(Object[] benMapArr) { MBeneficiarymapping benMapOBJ = new MBeneficiarymapping(); From ecabd888b8666c1df6431644e5dbf1df20f84766 Mon Sep 17 00:00:00 2001 From: Ravi Shanigarapu Date: Fri, 21 Mar 2025 13:59:13 +0530 Subject: [PATCH 02/14] removed unused imports --- src/main/java/com/iemr/common/identity/repo/BenIdentityRepo.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/java/com/iemr/common/identity/repo/BenIdentityRepo.java b/src/main/java/com/iemr/common/identity/repo/BenIdentityRepo.java index e20315c3..f194f027 100644 --- a/src/main/java/com/iemr/common/identity/repo/BenIdentityRepo.java +++ b/src/main/java/com/iemr/common/identity/repo/BenIdentityRepo.java @@ -33,7 +33,6 @@ import org.springframework.transaction.annotation.Transactional; import com.iemr.common.identity.domain.MBeneficiaryidentity; -import com.iemr.common.identity.domain.MBeneficiaryservicemapping; @Repository public interface BenIdentityRepo extends CrudRepository { From 4030d5635fa5f14c5b7eaff576409c4b6a0ed7fb Mon Sep 17 00:00:00 2001 From: Ravi Shanigarapu Date: Tue, 25 Mar 2025 17:59:47 +0530 Subject: [PATCH 03/14] removed unused imports --- .../java/com/iemr/common/identity/repo/BenFamilyMappingRepo.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/java/com/iemr/common/identity/repo/BenFamilyMappingRepo.java b/src/main/java/com/iemr/common/identity/repo/BenFamilyMappingRepo.java index f15a8e4d..3310adab 100644 --- a/src/main/java/com/iemr/common/identity/repo/BenFamilyMappingRepo.java +++ b/src/main/java/com/iemr/common/identity/repo/BenFamilyMappingRepo.java @@ -33,7 +33,6 @@ import org.springframework.transaction.annotation.Transactional; import com.iemr.common.identity.domain.MBeneficiaryfamilymapping; -import com.iemr.common.identity.domain.MBeneficiaryidentity; @Repository public interface BenFamilyMappingRepo extends CrudRepository { From 1d2e222f193c9e83cb113c641ae693e894f0c9e1 Mon Sep 17 00:00:00 2001 From: Ravi Shanigarapu Date: Wed, 2 Apr 2025 12:46:21 +0530 Subject: [PATCH 04/14] AMM-1252 --- .../iemr/common/identity/service/IdentityService.java | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/main/java/com/iemr/common/identity/service/IdentityService.java b/src/main/java/com/iemr/common/identity/service/IdentityService.java index c123eebb..21bfbc50 100644 --- a/src/main/java/com/iemr/common/identity/service/IdentityService.java +++ b/src/main/java/com/iemr/common/identity/service/IdentityService.java @@ -35,6 +35,7 @@ import javax.sql.DataSource; +import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; @@ -1607,13 +1608,15 @@ public List getBeneficiariesDeatilsByBenRegIdList(List floatList = new ArrayList<>(); - for (String str : stringNumbers) { - floatList.add(Float.parseFloat(str)); + if(!StringUtils.isEmpty(trimmedInput)) { + String[] stringNumbers = trimmedInput.split(",\\s*"); + for (String str : stringNumbers) { + floatList.add(Float.parseFloat(str)); + } } bdto.setFaceEmbedding(floatList); } From 5670ba579678da04efc23af359a50b14c5cb85cc Mon Sep 17 00:00:00 2001 From: Ravi Shanigarapu Date: Thu, 17 Apr 2025 17:58:32 +0530 Subject: [PATCH 05/14] swagger changes --- src/main/environment/1097_ci.properties | 6 +++++- src/main/environment/common_ci.properties | 5 ++++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/src/main/environment/1097_ci.properties b/src/main/environment/1097_ci.properties index a2ddb2ec..667dcd8f 100644 --- a/src/main/environment/1097_ci.properties +++ b/src/main/environment/1097_ci.properties @@ -5,4 +5,8 @@ spring.datasource.password=@env.DATABASE_IDENTITY_PASSWORD@ spring.datasource.driver-class-name=com.mysql.jdbc.Driver #ELK logging file name -logging.file.name=@env.IDENTITY_API_1097_LOGGING_FILE_NAME@ \ No newline at end of file +logging.file.name=@env.IDENTITY_API_1097_LOGGING_FILE_NAME@ + +springdoc.api-docs.enabled=@env.SWAGGER_DOC_ENABLED@ +springdoc.swagger-ui.enabled=@env.SWAGGER_DOC_ENABLED@ + diff --git a/src/main/environment/common_ci.properties b/src/main/environment/common_ci.properties index 641d6ca1..32cb2713 100644 --- a/src/main/environment/common_ci.properties +++ b/src/main/environment/common_ci.properties @@ -5,4 +5,7 @@ spring.datasource.password=@env.DATABASE_IDENTITY_PASSWORD@ spring.datasource.driver-class-name=com.mysql.jdbc.Driver #ELK logging file name -logging.file.name=@env.IDENTITY_API_LOGGING_FILE_NAME@ \ No newline at end of file +logging.file.name=@env.IDENTITY_API_LOGGING_FILE_NAME@ + +springdoc.api-docs.enabled=@env.SWAGGER_DOC_ENABLED@ +springdoc.swagger-ui.enabled=@env.SWAGGER_DOC_ENABLED@ From f679f75fbeb4aeb550fea19f56d6d20d8bdbad2f Mon Sep 17 00:00:00 2001 From: Ravi Shanigarapu Date: Thu, 15 May 2025 09:20:22 +0530 Subject: [PATCH 06/14] bug(Identity-API) Edit family tagging null condition added --- .../identity/service/familyTagging/FamilyTagServiceImpl.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/iemr/common/identity/service/familyTagging/FamilyTagServiceImpl.java b/src/main/java/com/iemr/common/identity/service/familyTagging/FamilyTagServiceImpl.java index f890f693..2a60571b 100644 --- a/src/main/java/com/iemr/common/identity/service/familyTagging/FamilyTagServiceImpl.java +++ b/src/main/java/com/iemr/common/identity/service/familyTagging/FamilyTagServiceImpl.java @@ -157,7 +157,7 @@ public String editFamilyDetails(String request) throws IEMRException { if (benFamilyRS != null) { if (benFamilyObj.getIsHeadOfTheFamily() != null && benFamilyObj.getMemberName() != null) { - if(benFamilyRS.getFamilyHeadName().trim().equals(benFamilyObj.getMemberName().trim()) && Boolean.TRUE.equals(!benFamilyObj.getIsHeadOfTheFamily())) + if(null != benFamilyRS.getFamilyHeadName() && benFamilyRS.getFamilyHeadName().trim().equals(benFamilyObj.getMemberName().trim()) && Boolean.TRUE.equals(!benFamilyObj.getIsHeadOfTheFamily())) benFamilyRS.setFamilyHeadName(""); else benFamilyRS.setFamilyHeadName(benFamilyObj.getMemberName()); From ea9d72cc08e8ae754d857ae0c215ed24b47f6ed9 Mon Sep 17 00:00:00 2001 From: Ravi Shanigarapu Date: Wed, 30 Jul 2025 11:58:55 +0530 Subject: [PATCH 07/14] Jwttoken implementation and Crossorigin changes. --- pom.xml | 20 +++ src/main/environment/1097_ci.properties | 6 +- src/main/environment/1097_docker.properties | 4 +- src/main/environment/1097_example.properties | 3 +- src/main/environment/common_ci.properties | 6 +- src/main/environment/common_docker.properties | 4 +- .../environment/common_example.properties | 3 +- .../common/identity/config/CorsConfig.java | 23 +++ .../controller/IdentityController.java | 22 --- .../FamilyTaggingController.java | 8 - .../controller/version/VersionController.java | 2 - .../com/iemr/common/identity/domain/User.java | 146 +++++++++++++++ .../common/identity/utils/CookieUtil.java | 48 +++++ .../common/identity/utils/FilterConfig.java | 26 +++ .../identity/utils/JwtAuthenticationUtil.java | 125 +++++++++++++ .../utils/JwtUserIdValidationFilter.java | 153 ++++++++++++++++ .../iemr/common/identity/utils/JwtUtil.java | 168 ++++++++++++++++++ .../common/identity/utils/TokenDenylist.java | 58 ++++++ .../identity/utils/UserAgentContext.java | 17 ++ .../AuthorizationHeaderRequestWrapper.java | 42 +++++ .../utils/http/HTTPRequestInterceptor.java | 2 +- .../identity/utils/redis/RedisConfig.java | 34 ++++ src/main/resources/application.properties | 14 +- 23 files changed, 883 insertions(+), 51 deletions(-) create mode 100644 src/main/java/com/iemr/common/identity/config/CorsConfig.java create mode 100644 src/main/java/com/iemr/common/identity/domain/User.java create mode 100644 src/main/java/com/iemr/common/identity/utils/CookieUtil.java create mode 100644 src/main/java/com/iemr/common/identity/utils/FilterConfig.java create mode 100644 src/main/java/com/iemr/common/identity/utils/JwtAuthenticationUtil.java create mode 100644 src/main/java/com/iemr/common/identity/utils/JwtUserIdValidationFilter.java create mode 100644 src/main/java/com/iemr/common/identity/utils/JwtUtil.java create mode 100644 src/main/java/com/iemr/common/identity/utils/TokenDenylist.java create mode 100644 src/main/java/com/iemr/common/identity/utils/UserAgentContext.java create mode 100644 src/main/java/com/iemr/common/identity/utils/http/AuthorizationHeaderRequestWrapper.java create mode 100644 src/main/java/com/iemr/common/identity/utils/redis/RedisConfig.java diff --git a/pom.xml b/pom.xml index ffbae092..dee19366 100644 --- a/pom.xml +++ b/pom.xml @@ -220,6 +220,26 @@ spring-web 6.1.12 + + + io.jsonwebtoken + jjwt-api + 0.12.6 + + + + io.jsonwebtoken + jjwt-impl + 0.12.6 + runtime + + + + io.jsonwebtoken + jjwt-jackson + 0.12.6 + runtime + diff --git a/src/main/environment/1097_ci.properties b/src/main/environment/1097_ci.properties index a2b79037..1bb80771 100644 --- a/src/main/environment/1097_ci.properties +++ b/src/main/environment/1097_ci.properties @@ -20,4 +20,8 @@ tm-url=@TM_API@ fhir-url=@FHIR_API@ # Redis Config -spring.redis.host=@env.REDIS_HOST@ \ No newline at end of file +spring.redis.host=@env.REDIS_HOST@ + +logging.file.name=@env.IDENTITY_API_1097_LOGGING_FILE_NAME@ + +cors.allowed-origins=@env.CORS_ALLOWED_ORIGINS@ \ No newline at end of file diff --git a/src/main/environment/1097_docker.properties b/src/main/environment/1097_docker.properties index 5510dfce..471665aa 100644 --- a/src/main/environment/1097_docker.properties +++ b/src/main/environment/1097_docker.properties @@ -19,4 +19,6 @@ tm-url=${TM_API} fhir-url=${FHIR_API} # Redis Config -spring.redis.host=${REDIS_HOST} \ No newline at end of file +spring.redis.host=${REDIS_HOST} + +cors.allowed-origins=${CORS_ALLOWED_ORIGINS} diff --git a/src/main/environment/1097_example.properties b/src/main/environment/1097_example.properties index 9eeee5c4..d1f66965 100644 --- a/src/main/environment/1097_example.properties +++ b/src/main/environment/1097_example.properties @@ -17,4 +17,5 @@ tm-url=http://localhost:8089/ fhir-url=http://localhost:8093/ # Redis Config -spring.redis.host=localhost \ No newline at end of file +spring.redis.host=localhost +cors.allowed-origins=http://localhost:* diff --git a/src/main/environment/common_ci.properties b/src/main/environment/common_ci.properties index debf33c4..0608a874 100644 --- a/src/main/environment/common_ci.properties +++ b/src/main/environment/common_ci.properties @@ -20,4 +20,8 @@ tm-url=@TM_API@ fhir-url=@FHIR_API@ # Redis Config -spring.redis.host=@env.REDIS_HOST@ \ No newline at end of file +spring.redis.host=@env.REDIS_HOST@ + +logging.file.name=@env.IDENTITY_API_LOGGING_FILE_NAME@ + +cors.allowed-origins=@env.CORS_ALLOWED_ORIGINS@ diff --git a/src/main/environment/common_docker.properties b/src/main/environment/common_docker.properties index 4409906e..90942c28 100644 --- a/src/main/environment/common_docker.properties +++ b/src/main/environment/common_docker.properties @@ -20,4 +20,6 @@ tm-url=${TM_API} fhir-url=${FHIR_API} # Redis Config -spring.redis.host=${REDIS_HOST} \ No newline at end of file +spring.redis.host=${REDIS_HOST} + +cors.allowed-origins=${CORS_ALLOWED_ORIGINS} diff --git a/src/main/environment/common_example.properties b/src/main/environment/common_example.properties index ca58c85c..ad15db00 100644 --- a/src/main/environment/common_example.properties +++ b/src/main/environment/common_example.properties @@ -16,4 +16,5 @@ tm-url=http://localhost:8089/ fhir-url=http://localhost:8093/ # Redis Config -spring.redis.host=localhost \ No newline at end of file +spring.redis.host=localhost +cors.allowed-origins=http://localhost:* diff --git a/src/main/java/com/iemr/common/identity/config/CorsConfig.java b/src/main/java/com/iemr/common/identity/config/CorsConfig.java new file mode 100644 index 00000000..f7a7cca3 --- /dev/null +++ b/src/main/java/com/iemr/common/identity/config/CorsConfig.java @@ -0,0 +1,23 @@ +package com.iemr.common.identity.config; + +import java.util.Arrays; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.servlet.config.annotation.CorsRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + +@Configuration +public class CorsConfig implements WebMvcConfigurer { + @Value("${cors.allowed-origins}") + private String allowedOrigins; + + @Override + public void addCorsMappings(CorsRegistry registry) { + registry.addMapping("/**") + .allowedOriginPatterns( + Arrays.stream(allowedOrigins.split(",")).map(String::trim).toArray(String[]::new)) + .allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS").allowedHeaders("*") + .exposedHeaders("Authorization", "Jwttoken").allowCredentials(true).maxAge(3600); + } +} diff --git a/src/main/java/com/iemr/common/identity/controller/IdentityController.java b/src/main/java/com/iemr/common/identity/controller/IdentityController.java index c3658c66..f80210b0 100644 --- a/src/main/java/com/iemr/common/identity/controller/IdentityController.java +++ b/src/main/java/com/iemr/common/identity/controller/IdentityController.java @@ -33,7 +33,6 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.web.bind.annotation.CrossOrigin; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; @@ -83,7 +82,6 @@ public class IdentityController { @Autowired IdentityMapper mapper; - @CrossOrigin(origins = { "*commonapi*" }) @Operation(summary = "Get beneficiaries by advance search") @PostMapping(path = "/advanceSearch", headers = "Authorization") public String getBeneficiaries( @@ -112,7 +110,6 @@ public String getBeneficiaries( return response; } - @CrossOrigin(origins = { "*commonapi*" }) @Operation(summary = "Search beneficiary based on beneficiary registration id") @PostMapping(path = "/getByBenRegId", headers = "Authorization") public String getBeneficiariesByBeneficiaryRegId( @@ -137,7 +134,6 @@ public String getBeneficiariesByBeneficiaryRegId( return response; } - @CrossOrigin(origins = { "*commonapi*" }) @Operation(summary = "Search identity based on beneficiary registration id") @PostMapping(path = "/getByBenId", headers = "Authorization") public String getBeneficiariesByBeneficiaryId( @@ -172,7 +168,6 @@ public String getBeneficiariesByBeneficiaryId( return response; } - @CrossOrigin(origins = { "*commonapi*" }) @Operation(summary = "Search beneficiary based on phone number") @PostMapping(path = "/getByPhoneNum", headers = "Authorization") public String getBeneficiariesByPhoneNum( @@ -203,7 +198,6 @@ public String getBeneficiariesByPhoneNum( return response; } - @CrossOrigin(origins = { "*commonapi*" }) @Operation(summary = "Search beneficiary based on health ID / ABHA Address") @PostMapping(path = "/getByAbhaAddress", headers = "Authorization") public String searhBeneficiaryByABHAAddress( @@ -235,7 +229,6 @@ public String searhBeneficiaryByABHAAddress( return response; } - @CrossOrigin(origins = { "*commonapi*" }) @Operation(summary = "Search beneficiary based on health ID number / ABHA ID number") @PostMapping(path = "/getByAbhaIdNo", headers = "Authorization") public String searhBeneficiaryByABHAIdNo( @@ -267,7 +260,6 @@ public String searhBeneficiaryByABHAIdNo( return response; } - @CrossOrigin(origins = { "*commonapi*" }) @Operation(summary = "Search beneficiary based on family id") @PostMapping(path = "/searchByFamilyId", headers = "Authorization") public String searhBeneficiaryByFamilyId( @@ -297,7 +289,6 @@ public String searhBeneficiaryByFamilyId( } // search beneficiary by lastModDate and districtID - @CrossOrigin(origins = { "*commonapi*" }) @Operation(summary ="Search beneficiary by villageId and last modified date-time") @PostMapping(path = "/searchByVillageIdAndLastModifiedDate") public String searchBeneficiaryByVillageIdAndLastModDate( @@ -322,7 +313,6 @@ public String searchBeneficiaryByVillageIdAndLastModDate( return response; } // search beneficiary by lastModDate and districtID - @CrossOrigin(origins = { "*commonapi*" }) @Operation(summary ="Get count of beneficiary by villageId and last modified date-time") @PostMapping(path = "/countBenByVillageIdAndLastModifiedDate") public String countBeneficiaryByVillageIdAndLastModDate( @@ -342,7 +332,6 @@ public String countBeneficiaryByVillageIdAndLastModDate( } return response; } - @CrossOrigin(origins = { "*commonapi*" }) @Operation(summary = "Search beneficiary based on government identity number") @PostMapping(path = "/searhByGovIdentity", headers = "Authorization") public String searhBeneficiaryByGovIdentity( @@ -376,7 +365,6 @@ public String searhBeneficiaryByGovIdentity( * @param identityEditData * @return */ - @CrossOrigin(origins = { "*commonapi*" }) @Operation(summary = "Edit identity by agent") @PostMapping(path = "/edit", headers = "Authorization") public String editIdentity(@Param(value = "{\r\n" + " \"eventTypeName\": \"String\",\r\n" @@ -508,7 +496,6 @@ public String editIdentity(@Param(value = "{\r\n" + " \"eventTypeName\": \"Stri * @param identityData * @return */ - @CrossOrigin(origins = { "*commonapi*" }) @Operation(summary = "Create identity by agent") @PostMapping(path = "/create", headers = "Authorization") public String createIdentity(@Param(value = "{\r\n" + " \"eventTypeName\": \"String\",\r\n" @@ -615,7 +602,6 @@ public String createIdentity(@Param(value = "{\r\n" + " \"eventTypeName\": \"St return response; } - @CrossOrigin(origins = { "*commonapi*" }) @Operation(summary = "Reserve identity by agent") @PostMapping(path = "/reserve", headers = "Authorization") public String reserveIdentity(@RequestBody String reserveIdentity) { @@ -635,7 +621,6 @@ public String reserveIdentity(@RequestBody String reserveIdentity) { return response; } - @CrossOrigin(origins = { "*commonapi*" }) @Operation(summary = "Unreserve identity by agent") @PostMapping(path = "/unreserve", headers = "Authorization") public String unreserveIdentity(@RequestBody String unreserve) { @@ -661,7 +646,6 @@ public String unreserveIdentity(@RequestBody String unreserve) { * @param benRegIds * @return */ - @CrossOrigin(origins = { "*commonapi*" }) @Operation(summary = "Get beneficiaries partial details by beneficiary registration id list") @PostMapping(path = "/getByPartialBenRegIdList", headers = "Authorization") public String getPartialBeneficiariesByBenRegIds( @@ -693,7 +677,6 @@ public String getPartialBeneficiariesByBenRegIds( * @param benRegIds * @return */ - @CrossOrigin(origins = { "*commonapi*" }) @Operation(summary = "Get beneficiaries by beneficiary registration id") @PostMapping(path = "/getByBenRegIdList", headers = "Authorization") public String getBeneficiariesByBenRegIds( @@ -792,7 +775,6 @@ public String getJsonAsString(Object obj) { return sb.toString(); } - @CrossOrigin(origins = { "*commonapi*" }) @Operation(summary = "Get finite beneficiaries") @PostMapping(path = "/finiteSearch", headers = "Authorization") public String getFiniteBeneficiaries(@RequestBody String searchFilter) { @@ -816,7 +798,6 @@ public String getFiniteBeneficiaries(@RequestBody String searchFilter) { } // New API for getting beneficiary image only. - @CrossOrigin(origins = { "*commonapi*" }) @Operation(summary = "Get beneficiary image by beneficiary registration id") @PostMapping(path = "/benImageByBenRegID", headers = "Authorization") public String getBeneficiaryImageByBenRegID(@RequestBody String identityData) { @@ -830,7 +811,6 @@ public String getBeneficiaryImageByBenRegID(@RequestBody String identityData) { return benImage; } - @CrossOrigin(origins = { "*commonapi*" }) @Operation(summary = "Edit education or community by agent") @PostMapping(path = "/editEducationOrCommunity", headers = "Authorization") public String editIdentityEducationOrCommunity(@Param(value = "{\r\n" @@ -957,7 +937,6 @@ public String editIdentityEducationOrCommunity(@Param(value = "{\r\n" } } - @CrossOrigin() @Operation(summary = "Check available beneficary id in local server") @GetMapping(path = "/checkAvailablBenIDLocalServer", headers = "Authorization") public String checkAvailablBenIDLocalServer() { @@ -972,7 +951,6 @@ public String checkAvailablBenIDLocalServer() { return response.toString(); } - @CrossOrigin(origins = { "*commonapi*" }) @Operation(summary = "Save server generated beneficiary ID & beneficiary registration ID to local server") @PostMapping(path = "/saveGeneratedBenIDToLocalServer", headers = "Authorization", consumes = "application/json", produces = "application/json") public String saveGeneratedBenIDToLocalServer( diff --git a/src/main/java/com/iemr/common/identity/controller/familyTagging/FamilyTaggingController.java b/src/main/java/com/iemr/common/identity/controller/familyTagging/FamilyTaggingController.java index b1f117c7..19323611 100644 --- a/src/main/java/com/iemr/common/identity/controller/familyTagging/FamilyTaggingController.java +++ b/src/main/java/com/iemr/common/identity/controller/familyTagging/FamilyTaggingController.java @@ -24,7 +24,6 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.web.bind.annotation.CrossOrigin; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; @@ -35,7 +34,6 @@ import io.swagger.v3.oas.annotations.Operation; -@CrossOrigin @RestController @RequestMapping({ "/family" }) public class FamilyTaggingController { @@ -43,7 +41,6 @@ public class FamilyTaggingController { @Autowired private FamilyTagService familyTagService; - @CrossOrigin() @Operation(summary = "Create and modify family tagging") @PostMapping(value = { "/addTag" }, consumes = "application/json", produces = "application/json") public String saveFamilyTagging(@RequestBody String comingReq) { @@ -59,7 +56,6 @@ public String saveFamilyTagging(@RequestBody String comingReq) { return response.toString(); } - @CrossOrigin() @Operation(summary = "Create family") @PostMapping(value = { "/createFamily" }, consumes = "application/json", produces = "application/json") public String createFamily(@RequestBody String comingReq) { @@ -75,7 +71,6 @@ public String createFamily(@RequestBody String comingReq) { return response.toString(); } - @CrossOrigin() @Operation(summary = "Search family") @PostMapping(value = { "/searchFamily" }, consumes = "application/json", produces = "application/json") public String searchFamily(@RequestBody String comingReq) { @@ -91,7 +86,6 @@ public String searchFamily(@RequestBody String comingReq) { return response.toString(); } - @CrossOrigin() @Operation(summary = "Get family members details") @PostMapping(value = { "/getFamilyDetails" }, consumes = "application/json", produces = "application/json") public String getFamilyDatails(@RequestBody String comingReq) { @@ -107,7 +101,6 @@ public String getFamilyDatails(@RequestBody String comingReq) { return response.toString(); } - @CrossOrigin() @Operation(summary = "Untag beneficiary from a family") @PostMapping(value = { "/untag" }, consumes = "application/json", produces = "application/json") public String untagFamily(@RequestBody String comingReq) { @@ -123,7 +116,6 @@ public String untagFamily(@RequestBody String comingReq) { return response.toString(); } - @CrossOrigin() @Operation(summary = "Edit beneficiary family details") @PostMapping(value = { "/editFamilyTagging" }, consumes = "application/json", produces = "application/json") public String editFamilyDetails(@RequestBody String comingReq) { diff --git a/src/main/java/com/iemr/common/identity/controller/version/VersionController.java b/src/main/java/com/iemr/common/identity/controller/version/VersionController.java index fdf973cd..4435c3db 100644 --- a/src/main/java/com/iemr/common/identity/controller/version/VersionController.java +++ b/src/main/java/com/iemr/common/identity/controller/version/VersionController.java @@ -28,7 +28,6 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.springframework.web.bind.annotation.CrossOrigin; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; @@ -41,7 +40,6 @@ public class VersionController { private Logger logger = LoggerFactory.getLogger(this.getClass().getSimpleName()); - @CrossOrigin() @Operation(summary = "Get version information") @GetMapping(value = "/version",consumes = "application/json", produces = "application/json") public String versionInformation() { diff --git a/src/main/java/com/iemr/common/identity/domain/User.java b/src/main/java/com/iemr/common/identity/domain/User.java new file mode 100644 index 00000000..44d479d7 --- /dev/null +++ b/src/main/java/com/iemr/common/identity/domain/User.java @@ -0,0 +1,146 @@ +package com.iemr.common.identity.domain; + +import java.sql.Timestamp; +import java.time.LocalDate; + +import com.google.gson.annotations.Expose; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; +import lombok.Data; +@Entity +@Table(name = "m_User",schema = "db_iemr") +@Data +public class User { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Expose + @Column(name="UserID") + private Integer userID; + @Expose + @Column(name="TitleID") + private Integer titleID; + @Expose + @Column(name="FirstName") + private String firstName; + @Expose + @Column(name="MiddleName") + private String middleName; + @Expose + @Column(name="LastName") + private String lastName; + @Expose + @Column(name="GenderID") + private Short genderID; + + @Expose + @Column(name="MaritalStatusID") + private Integer maritalStatusID; + @Expose + @Column(name="DesignationID") + private Integer designationID; + + @Expose + @Column(name="AadhaarNo") + private String aadhaarNo; + @Expose + @Column(name="PAN") + private String pAN; + @Expose + @Column(name="DOB") + private LocalDate dOB; + @Expose + @Column(name="DOJ") + private LocalDate dOJ; + @Expose + @Column(name="QualificationID") + private Integer qualificationID; + @Expose + @Column(name="HealthProfessionalID") + private String healthProfessionalID; + @Expose + @Column(name="UserName") + private String userName; + @Expose + @Column(name="Password") + private String password; + @Expose + @Column(name="IsExternal") + private Boolean isExternal; + @Expose + @Column(name="AgentID") + private String agentID; + @Expose + @Column(name="AgentPassword") + private String agentPassword; + @Expose + @Column(name="EmailID") + private String emailID; + @Expose + @Column(name="StatusID") + private Integer statusID; + @Expose + @Column(name="EmergencyContactPerson") + private String emergencyContactPerson; + @Expose + @Column(name="EmergencyContactNo") + private String emergencyContactNo; + @Expose + @Column(name="IsSupervisor") + private Boolean isSupervisor; + @Expose + @Column(name="Deleted",insertable = false, updatable = true) + private Boolean deleted; + @Expose + @Column(name="CreatedBy") + private String createdBy; + @Expose + @Column(name="EmployeeID") + private String employeeID; + @Expose + @Column(name="CreatedDate",insertable = false, updatable = false) + private Timestamp createdDate; + @Expose + @Column(name="ModifiedBy") + private String modifiedBy; + @Expose + @Column(name="LastModDate",insertable = false, updatable = false) + private Timestamp lastModDate; + + @Expose + @Column(name="Remarks") + private String remarks; + + @Expose + @Column(name="ContactNo") + private String contactNo; + + + @Expose + @Column(name="IsProviderAdmin") + private Boolean isProviderAdmin; + + @Expose + @Column(name="ServiceProviderID") + private Integer serviceProviderID; + + + + @Expose + @Column(name = "failed_attempt", insertable = false) + private Integer failedAttempt; + public User() { + // TODO Auto-generated constructor stub + } + + public User(Integer userID, String userName) { + // TODO Auto-generated constructor stub + this.userID=userID; + this.userName=userName; + } + +} diff --git a/src/main/java/com/iemr/common/identity/utils/CookieUtil.java b/src/main/java/com/iemr/common/identity/utils/CookieUtil.java new file mode 100644 index 00000000..8c196ee6 --- /dev/null +++ b/src/main/java/com/iemr/common/identity/utils/CookieUtil.java @@ -0,0 +1,48 @@ +package com.iemr.common.identity.utils; + +import java.util.Arrays; +import java.util.Optional; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; + +import jakarta.servlet.http.Cookie; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; + +@Service +public class CookieUtil { + @Value("${isProduction}") + private Boolean isProduction; + + public Optional getCookieValue(HttpServletRequest request, String cookieName) { + Cookie[] cookies = request.getCookies(); + if (cookies != null) { + for (Cookie cookie : cookies) { + if (cookieName.equals(cookie.getName())) { + return Optional.of(cookie.getValue()); + } + } + } + return Optional.empty(); + } + + public void addJwtTokenToCookie(String Jwttoken, HttpServletResponse response, HttpServletRequest request) { + Cookie cookie = new Cookie("Jwttoken", Jwttoken); + cookie.setHttpOnly(true); + cookie.setMaxAge(60 * 60 * 24); + cookie.setPath("/"); + if ("https".equalsIgnoreCase(request.getScheme())) { + cookie.setSecure(true); + } + response.addCookie(cookie); + } + + public static String getJwtTokenFromCookie(HttpServletRequest request) { + if (request.getCookies() == null) { + return null; + } + return Arrays.stream(request.getCookies()).filter(cookie -> "Jwttoken".equalsIgnoreCase(cookie.getName())) + .map(Cookie::getValue).findFirst().orElse(null); + } +} diff --git a/src/main/java/com/iemr/common/identity/utils/FilterConfig.java b/src/main/java/com/iemr/common/identity/utils/FilterConfig.java new file mode 100644 index 00000000..0a8b0904 --- /dev/null +++ b/src/main/java/com/iemr/common/identity/utils/FilterConfig.java @@ -0,0 +1,26 @@ +package com.iemr.common.identity.utils; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.web.servlet.FilterRegistrationBean; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.Ordered; + +@Configuration +public class FilterConfig { + @Value("${cors.allowed-origins}") + private String allowedOrigins; + + @Bean + public FilterRegistrationBean jwtUserIdValidationFilter( + JwtAuthenticationUtil jwtAuthenticationUtil) { + FilterRegistrationBean registrationBean = new FilterRegistrationBean<>(); + + JwtUserIdValidationFilter filter = new JwtUserIdValidationFilter(jwtAuthenticationUtil, allowedOrigins); + registrationBean.setFilter(filter); + registrationBean.setOrder(Ordered.HIGHEST_PRECEDENCE); + registrationBean.addUrlPatterns("/*"); // Apply filter to all API endpoints + return registrationBean; + } + +} diff --git a/src/main/java/com/iemr/common/identity/utils/JwtAuthenticationUtil.java b/src/main/java/com/iemr/common/identity/utils/JwtAuthenticationUtil.java new file mode 100644 index 00000000..a157ede5 --- /dev/null +++ b/src/main/java/com/iemr/common/identity/utils/JwtAuthenticationUtil.java @@ -0,0 +1,125 @@ +package com.iemr.common.identity.utils; + +import java.util.Optional; +import java.util.concurrent.TimeUnit; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.jdbc.core.BeanPropertyRowMapper; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.stereotype.Component; + +import com.iemr.common.identity.domain.User; +import com.iemr.common.identity.exception.IEMRException; +import com.iemr.common.identity.repo.BenIdentityRepo; + +import io.jsonwebtoken.Claims; +import jakarta.servlet.http.HttpServletRequest; + +@Component +public class JwtAuthenticationUtil { + @Autowired + private CookieUtil cookieUtil; + @Autowired + private JwtUtil jwtUtil; + @Autowired + private RedisTemplate redisTemplate; + @Autowired + private BenIdentityRepo benIdentityRepo; + + @Autowired + private JdbcTemplate jdbcTemplate; + + private final Logger logger = LoggerFactory.getLogger(this.getClass().getName()); + + public JwtAuthenticationUtil(CookieUtil cookieUtil, JwtUtil jwtUtil) { + this.cookieUtil = cookieUtil; + this.jwtUtil = jwtUtil; + } + + public ResponseEntity validateJwtToken(HttpServletRequest request) { + Optional jwtTokenOpt = cookieUtil.getCookieValue(request, "Jwttoken"); + + if (jwtTokenOpt.isEmpty()) { + return ResponseEntity.status(HttpStatus.UNAUTHORIZED) + .body("Error 401: Unauthorized - JWT Token is not set!"); + } + + String jwtToken = jwtTokenOpt.get(); + + // Validate the token + Claims claims = jwtUtil.validateToken(jwtToken); + if (claims == null) { + return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body("Error 401: Unauthorized - Invalid JWT Token!"); + } + + // Extract username from token + String usernameFromToken = claims.getSubject(); + if (usernameFromToken == null || usernameFromToken.isEmpty()) { + return ResponseEntity.status(HttpStatus.UNAUTHORIZED) + .body("Error 401: Unauthorized - Username is missing!"); + } + + // Return the username if valid + return ResponseEntity.ok(usernameFromToken); + } + + public boolean validateUserIdAndJwtToken(String jwtToken) throws IEMRException { + try { + Claims claims = jwtUtil.validateToken(jwtToken); + if (claims == null) { + throw new IEMRException("Invalid JWT token."); + } + String userId = claims.get("userId", String.class); + User user = getUserFromCache(userId); + if (user == null) { + user = fetchUserFromDB(userId); + } + if (user == null) { + throw new IEMRException("Invalid User ID."); + } + + return true; // Valid userId and JWT token + } catch (Exception e) { + logger.error("Validation failed: " + e.getMessage(), e); + throw new IEMRException("Validation error: " + e.getMessage(), e); + } + } + + private User getUserFromCache(String userId) { + String redisKey = "user_" + userId; // The Redis key format + User user = (User) redisTemplate.opsForValue().get(redisKey); + + if (user == null) { + logger.warn("User not found in Redis. Will try to fetch from DB."); + } else { + logger.info("User fetched successfully from Redis."); + } + + return user; // Returns null if not found + } + + private User fetchUserFromDB(String userId) { + String redisKey = "user_" + userId; // Redis key format + User user = jdbcTemplate.queryForObject( + "SELECT * FROM db_iemr.m_user WHERE UserID = ?", + new BeanPropertyRowMapper<>(User.class), + userId + ); + if (user != null) { + User userHash = new User(); + userHash.setUserID(user.getUserID()); + userHash.setUserName(user.getUserName()); + redisTemplate.opsForValue().set(redisKey, userHash, 30, TimeUnit.MINUTES); + logger.info("User stored in Redis with key: " + redisKey); + return user; + } else { + logger.warn("User not found for userId: " + userId); + } + return null; + } +} diff --git a/src/main/java/com/iemr/common/identity/utils/JwtUserIdValidationFilter.java b/src/main/java/com/iemr/common/identity/utils/JwtUserIdValidationFilter.java new file mode 100644 index 00000000..aa6dbd79 --- /dev/null +++ b/src/main/java/com/iemr/common/identity/utils/JwtUserIdValidationFilter.java @@ -0,0 +1,153 @@ +package com.iemr.common.identity.utils; + +import java.io.IOException; +import java.util.Arrays; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.iemr.common.identity.utils.http.AuthorizationHeaderRequestWrapper; + +import jakarta.servlet.Filter; +import jakarta.servlet.FilterChain; +import jakarta.servlet.ServletException; +import jakarta.servlet.ServletRequest; +import jakarta.servlet.ServletResponse; +import jakarta.servlet.http.Cookie; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; + +public class JwtUserIdValidationFilter implements Filter { + private final JwtAuthenticationUtil jwtAuthenticationUtil; + private final Logger logger = LoggerFactory.getLogger(this.getClass().getName()); + private final String allowedOrigins; + + public JwtUserIdValidationFilter(JwtAuthenticationUtil jwtAuthenticationUtil, String allowedOrigins) { + this.jwtAuthenticationUtil = jwtAuthenticationUtil; + this.allowedOrigins = allowedOrigins; + } + + @Override + public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) + throws IOException, ServletException { + HttpServletRequest request = (HttpServletRequest) servletRequest; + HttpServletResponse response = (HttpServletResponse) servletResponse; + + String origin = request.getHeader("Origin"); + + logger.debug("Incoming Origin: {}", origin); + logger.debug("Allowed Origins Configured: {}", allowedOrigins); + + if (origin != null && isOriginAllowed(origin)) { + response.setHeader("Access-Control-Allow-Origin", origin); + response.setHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS"); + response.setHeader("Access-Control-Allow-Headers", "Authorization, Content-Type, Accept, Jwttoken"); + response.setHeader("Access-Control-Allow-Credentials", "true"); + } else { + logger.warn("Origin [{}] is NOT allowed. CORS headers NOT added.", origin); + } + + if ("OPTIONS".equalsIgnoreCase(request.getMethod())) { + logger.info("OPTIONS request - skipping JWT validation"); + response.setStatus(HttpServletResponse.SC_OK); + return; + } + + String path = request.getRequestURI(); + logger.info("JwtUserIdValidationFilter invoked for path: " + path); + + // Log cookies for debugging + Cookie[] cookies = request.getCookies(); + if (cookies != null) { + for (Cookie cookie : cookies) { + if ("userId".equalsIgnoreCase(cookie.getName())) { + logger.warn("userId found in cookies! Clearing it..."); + clearUserIdCookie(response); // Explicitly remove userId cookie + } + } + } else { + logger.info("No cookies found in the request"); + } + + // Log headers for debugging + logger.info("JWT token from header: "); + + try { + String jwtFromCookie = getJwtTokenFromCookies(request); + String jwtFromHeader = request.getHeader("JwtToken"); + String authHeader = request.getHeader("Authorization"); + String jwtToken = jwtFromCookie != null ? jwtFromCookie : jwtFromHeader; + if (jwtToken != null) { + logger.info("Validating JWT token from cookie"); + if (jwtAuthenticationUtil.validateUserIdAndJwtToken(jwtToken)) { + AuthorizationHeaderRequestWrapper authorizationHeaderRequestWrapper = new AuthorizationHeaderRequestWrapper( + request, ""); + filterChain.doFilter(authorizationHeaderRequestWrapper, servletResponse); + return; + } + } else { + String userAgent = request.getHeader("User-Agent"); + logger.info("User-Agent: " + userAgent); + if (userAgent != null && isMobileClient(userAgent) && authHeader != null) { + try { + logger.info("Common-API incoming userAget : " + userAgent); + UserAgentContext.setUserAgent(userAgent); + filterChain.doFilter(servletRequest, servletResponse); + } finally { + UserAgentContext.clear(); + } + return; + } + } + + logger.warn("No valid authentication token found"); + response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Unauthorized: Invalid or missing token"); + } catch (Exception e) { + logger.error("Authorization error: ", e); + response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Authorization error: " + e.getMessage()); + } + } + + private boolean isOriginAllowed(String origin) { + if (origin == null || allowedOrigins == null || allowedOrigins.trim().isEmpty()) { + logger.warn("No allowed origins configured or origin is null"); + return false; + } + + return Arrays.stream(allowedOrigins.split(",")).map(String::trim).anyMatch(pattern -> { + String regex = pattern.replace(".", "\\.").replace("*", ".*").replace("http://localhost:.*", + "http://localhost:\\d+"); // special case for wildcard port + + boolean matched = origin.matches(regex); + return matched; + }); + } + + private boolean isMobileClient(String userAgent) { + if (userAgent == null) + return false; + userAgent = userAgent.toLowerCase(); + return userAgent.contains("okhttp"); // iOS (custom clients) + } + + private String getJwtTokenFromCookies(HttpServletRequest request) { + Cookie[] cookies = request.getCookies(); + if (cookies != null) { + for (Cookie cookie : cookies) { + if (cookie.getName().equalsIgnoreCase("Jwttoken")) { + return cookie.getValue(); + } + } + } + return null; + } + + private void clearUserIdCookie(HttpServletResponse response) { + Cookie cookie = new Cookie("userId", null); + cookie.setPath("/"); + cookie.setHttpOnly(true); + cookie.setSecure(true); + cookie.setMaxAge(0); // Invalidate the cookie + response.addCookie(cookie); + } +} diff --git a/src/main/java/com/iemr/common/identity/utils/JwtUtil.java b/src/main/java/com/iemr/common/identity/utils/JwtUtil.java new file mode 100644 index 00000000..d15e08e7 --- /dev/null +++ b/src/main/java/com/iemr/common/identity/utils/JwtUtil.java @@ -0,0 +1,168 @@ +package com.iemr.common.identity.utils; + +import java.util.Date; +import java.util.UUID; +import java.util.function.Function; + +import javax.crypto.SecretKey; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +import ch.qos.logback.classic.Logger; +import io.jsonwebtoken.Claims; +import io.jsonwebtoken.ExpiredJwtException; +import io.jsonwebtoken.Jwts; +import io.jsonwebtoken.MalformedJwtException; +import io.jsonwebtoken.UnsupportedJwtException; +import io.jsonwebtoken.security.Keys; +import io.jsonwebtoken.security.SignatureException; +@Component +public class JwtUtil { + @Value("${jwt.secret}") + private String SECRET_KEY; + + @Value("${jwt.access.expiration}") + private long ACCESS_EXPIRATION_TIME; + + @Value("${jwt.refresh.expiration}") + private long REFRESH_EXPIRATION_TIME; + + @Autowired + private TokenDenylist tokenDenylist; + + private SecretKey getSigningKey() { + if (SECRET_KEY == null || SECRET_KEY.isEmpty()) { + throw new IllegalStateException("JWT secret key is not set in application.properties"); + } + return Keys.hmacShaKeyFor(SECRET_KEY.getBytes()); + } + + /** + * Generate an access token. + * + * @param username the username of the user + * @param userId the user ID + * @return the generated JWT access token + */ + public String generateToken(String username, String userId) { + return buildToken(username, userId, "access", ACCESS_EXPIRATION_TIME); + } + + /** + * Generate a refresh token. + * + * @param username the username of the user + * @param userId the user ID + * @return the generated JWT refresh token + */ + public String generateRefreshToken(String username, String userId) { + return buildToken(username, userId, "refresh", REFRESH_EXPIRATION_TIME); + } + + /** + * Build a JWT token with the specified parameters. + * + * @param username the username of the user + * @param userId the user ID + * @param tokenType the type of the token (access or refresh) + * @param expiration the expiration time of the token in milliseconds + * @return the generated JWT token + */ + private String buildToken(String username, String userId, String tokenType, long expiration) { + return Jwts.builder() + .subject(username) + .claim("userId", userId) + .claim("token_type", tokenType) + .id(UUID.randomUUID().toString()) + .issuedAt(new Date()) + .expiration(new Date(System.currentTimeMillis() + expiration)) + .signWith(getSigningKey()) + .compact(); + } + + /** + * Validate the JWT token, checking if it is expired and if it's blacklisted + * @param token the JWT token + * @return Claims if valid, null if invalid (expired or denylisted) + */ + public Claims validateToken(String token) { + try { + Claims claims = Jwts.parser().verifyWith(getSigningKey()).build().parseSignedClaims(token).getPayload(); + String jti = claims.getId(); + + // Check if token is denylisted (only if jti exists) + if (jti != null && tokenDenylist.isTokenDenylisted(jti)) { + return null; + } + return claims; + } catch (ExpiredJwtException ex) { + + return null; // Token is expired, so return null + } catch (UnsupportedJwtException | MalformedJwtException | SignatureException | IllegalArgumentException ex) { + return null; // Return null for any other JWT-related issue (invalid format, bad signature, etc.) + } + } + + /** + * Extract claims from the token + * @param token the JWT token + * @return all claims from the token + */ + public Claims getAllClaimsFromToken(String token) { + return Jwts.parser() + .verifyWith(getSigningKey()) + .build() + .parseSignedClaims(token) + .getPayload(); + + } + + /** + * Extract a specific claim from the token using a function + * @param token the JWT token + * @param claimsResolver the function to extract the claim + * @param the type of the claim + * @return the extracted claim + */ + public T getClaimFromToken(String token, Function claimsResolver) { + final Claims claims = getAllClaimsFromToken(token); + return claimsResolver.apply(claims); + } + + /** + * Get the JWT ID (JTI) from the token + * @param token the JWT token + * @return the JWT ID + */ + public String getJtiFromToken(String token) { + return getAllClaimsFromToken(token).getId(); + } + + /** + * Get the username from the token + * @param token the JWT token + * @return the username + */ + public String getUsernameFromToken(String token) { + return getAllClaimsFromToken(token).getSubject(); + } + + /** + * Get the user ID from the token + * @param token the JWT token + * @return the user ID + */ + public String getUserIdFromToken(String token) { + return getAllClaimsFromToken(token).get("userId", String.class); + } + + /** + * Get the expiration time of the refresh token + * @return the expiration time in milliseconds + */ + public long getRefreshTokenExpiration() { + return REFRESH_EXPIRATION_TIME; + } +} diff --git a/src/main/java/com/iemr/common/identity/utils/TokenDenylist.java b/src/main/java/com/iemr/common/identity/utils/TokenDenylist.java new file mode 100644 index 00000000..a5b0fd09 --- /dev/null +++ b/src/main/java/com/iemr/common/identity/utils/TokenDenylist.java @@ -0,0 +1,58 @@ +package com.iemr.common.identity.utils; + +import java.util.concurrent.TimeUnit; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.stereotype.Component; +@Component +public class TokenDenylist { + private final Logger logger = LoggerFactory.getLogger(this.getClass().getName()); + + private static final String PREFIX = "denied_"; + + @Autowired + private RedisTemplate redisTemplate; + + private String getKey(String jti) { + return PREFIX + jti; + } + + public void addTokenToDenylist(String jti, Long expirationTime) { + if (jti == null || jti.trim().isEmpty()) { + return; + } + if (expirationTime == null || expirationTime <= 0) { + throw new IllegalArgumentException("Expiration time must be positive"); + } + try { + String key = getKey(jti); // Use helper method to get the key + redisTemplate.opsForValue().set(key, " ", expirationTime, TimeUnit.MILLISECONDS); + } catch (Exception e) { + throw new RuntimeException("Failed to denylist token", e); + } + } + + public boolean isTokenDenylisted(String jti) { + if (jti == null || jti.trim().isEmpty()) { + return false; + } + try { + String key = getKey(jti); // Use helper method to get the key + return Boolean.TRUE.equals(redisTemplate.hasKey(key)); + } catch (Exception e) { + logger.error("Failed to check denylist status for jti: " + jti, e); + return false; + } + } + + // Remove a token's jti from the denylist (Redis) + public void removeTokenFromDenylist(String jti) { + if (jti != null && !jti.trim().isEmpty()) { + String key = getKey(jti); + redisTemplate.delete(key); + } + } +} diff --git a/src/main/java/com/iemr/common/identity/utils/UserAgentContext.java b/src/main/java/com/iemr/common/identity/utils/UserAgentContext.java new file mode 100644 index 00000000..2fd2ef21 --- /dev/null +++ b/src/main/java/com/iemr/common/identity/utils/UserAgentContext.java @@ -0,0 +1,17 @@ +package com.iemr.common.identity.utils; + +public class UserAgentContext { + private static final ThreadLocal userAgentHolder = new ThreadLocal<>(); + + public static void setUserAgent(String userAgent) { + userAgentHolder.set(userAgent); + } + + public static String getUserAgent() { + return userAgentHolder.get(); + } + + public static void clear() { + userAgentHolder.remove(); + } +} diff --git a/src/main/java/com/iemr/common/identity/utils/http/AuthorizationHeaderRequestWrapper.java b/src/main/java/com/iemr/common/identity/utils/http/AuthorizationHeaderRequestWrapper.java new file mode 100644 index 00000000..d748de2a --- /dev/null +++ b/src/main/java/com/iemr/common/identity/utils/http/AuthorizationHeaderRequestWrapper.java @@ -0,0 +1,42 @@ +package com.iemr.common.identity.utils.http; + +import java.util.Collections; +import java.util.Enumeration; +import java.util.List; + +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletRequestWrapper; + +public class AuthorizationHeaderRequestWrapper extends HttpServletRequestWrapper { + private final String Authorization; + + public AuthorizationHeaderRequestWrapper(HttpServletRequest request, String authHeaderValue) { + super(request); + this.Authorization = authHeaderValue; + } + + @Override + public String getHeader(String name) { + if ("Authorization".equalsIgnoreCase(name)) { + return Authorization; + } + return super.getHeader(name); + } + + @Override + public Enumeration getHeaders(String name) { + if ("Authorization".equalsIgnoreCase(name)) { + return Collections.enumeration(Collections.singletonList(Authorization)); + } + return super.getHeaders(name); + } + + @Override + public Enumeration getHeaderNames() { + List names = Collections.list(super.getHeaderNames()); + if (!names.contains("Authorization")) { + names.add("Authorization"); + } + return Collections.enumeration(names); + } +} diff --git a/src/main/java/com/iemr/common/identity/utils/http/HTTPRequestInterceptor.java b/src/main/java/com/iemr/common/identity/utils/http/HTTPRequestInterceptor.java index 865df356..7156102c 100644 --- a/src/main/java/com/iemr/common/identity/utils/http/HTTPRequestInterceptor.java +++ b/src/main/java/com/iemr/common/identity/utils/http/HTTPRequestInterceptor.java @@ -102,7 +102,7 @@ public void postHandle(HttpServletRequest request, HttpServletResponse response, else authorization = postAuth; logger.debug("RequestURI::" + request.getRequestURI() + " || Authorization ::" + authorization); - if (authorization != null) { + if (authorization != null && !authorization.isEmpty()) { sessionObject.updateSessionObject(authorization, sessionObject.getSessionObject(authorization)); } } catch (Exception e) { diff --git a/src/main/java/com/iemr/common/identity/utils/redis/RedisConfig.java b/src/main/java/com/iemr/common/identity/utils/redis/RedisConfig.java new file mode 100644 index 00000000..d1ad56ac --- /dev/null +++ b/src/main/java/com/iemr/common/identity/utils/redis/RedisConfig.java @@ -0,0 +1,34 @@ +package com.iemr.common.identity.utils.redis; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.data.redis.connection.RedisConnectionFactory; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer; +import org.springframework.data.redis.serializer.StringRedisSerializer; +import org.springframework.session.data.redis.config.ConfigureRedisAction; + +import com.iemr.common.identity.domain.User; + +@Configuration +public class RedisConfig { + @Bean + public ConfigureRedisAction configureRedisAction() { + return ConfigureRedisAction.NO_OP; + } + + @Bean + public RedisTemplate redisTemplate(RedisConnectionFactory factory) { + RedisTemplate template = new RedisTemplate<>(); + template.setConnectionFactory(factory); + + // Use StringRedisSerializer for keys (userId) + template.setKeySerializer(new StringRedisSerializer()); + + // Use Jackson2JsonRedisSerializer for values (Users objects) + Jackson2JsonRedisSerializer serializer = new Jackson2JsonRedisSerializer<>(User.class); + template.setValueSerializer(serializer); + + return template; + } +} diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index a0828d00..1780d8b6 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -51,15 +51,5 @@ getHealthID=healthID/getBenhealthID spring.main.allow-bean-definition-overriding=true spring.main.allow-circular-references=true #springfox.documentation.swagger-ui.enabled=true - - - - - - - - - - - - +jwt.access.expiration=86400000 +jwt.refresh.expiration=604800000 From 9fd406ee1af14c1c9f0f984278fb2483e31fd4b3 Mon Sep 17 00:00:00 2001 From: Ravi Shanigarapu Date: Wed, 30 Jul 2025 13:10:22 +0530 Subject: [PATCH 08/14] code rabbit comments addressed --- src/main/environment/1097_ci.properties | 4 +- src/main/environment/common_ci.properties | 2 - .../common/identity/config/CorsConfig.java | 6 +-- .../common/identity/utils/CookieUtil.java | 12 ++---- .../identity/utils/JwtAuthenticationUtil.java | 28 ++++++------- .../utils/JwtUserIdValidationFilter.java | 1 + .../iemr/common/identity/utils/JwtUtil.java | 39 +++++++++---------- .../common/identity/utils/TokenDenylist.java | 36 ++++++++++------- .../identity/utils/UserAgentContext.java | 9 +++-- .../exception/TokenDenylistException.java | 7 ++++ .../AuthorizationHeaderRequestWrapper.java | 8 ++-- .../identity/utils/redis/RedisConfig.java | 8 ++++ 12 files changed, 85 insertions(+), 75 deletions(-) create mode 100644 src/main/java/com/iemr/common/identity/utils/exception/TokenDenylistException.java diff --git a/src/main/environment/1097_ci.properties b/src/main/environment/1097_ci.properties index 1bb80771..fba22112 100644 --- a/src/main/environment/1097_ci.properties +++ b/src/main/environment/1097_ci.properties @@ -22,6 +22,4 @@ fhir-url=@FHIR_API@ # Redis Config spring.redis.host=@env.REDIS_HOST@ -logging.file.name=@env.IDENTITY_API_1097_LOGGING_FILE_NAME@ - -cors.allowed-origins=@env.CORS_ALLOWED_ORIGINS@ \ No newline at end of file +cors.allowed-origins=@env.CORS_ALLOWED_ORIGINS@ diff --git a/src/main/environment/common_ci.properties b/src/main/environment/common_ci.properties index 0608a874..ab01423a 100644 --- a/src/main/environment/common_ci.properties +++ b/src/main/environment/common_ci.properties @@ -22,6 +22,4 @@ fhir-url=@FHIR_API@ # Redis Config spring.redis.host=@env.REDIS_HOST@ -logging.file.name=@env.IDENTITY_API_LOGGING_FILE_NAME@ - cors.allowed-origins=@env.CORS_ALLOWED_ORIGINS@ diff --git a/src/main/java/com/iemr/common/identity/config/CorsConfig.java b/src/main/java/com/iemr/common/identity/config/CorsConfig.java index f7a7cca3..d52f7e35 100644 --- a/src/main/java/com/iemr/common/identity/config/CorsConfig.java +++ b/src/main/java/com/iemr/common/identity/config/CorsConfig.java @@ -14,9 +14,9 @@ public class CorsConfig implements WebMvcConfigurer { @Override public void addCorsMappings(CorsRegistry registry) { - registry.addMapping("/**") - .allowedOriginPatterns( - Arrays.stream(allowedOrigins.split(",")).map(String::trim).toArray(String[]::new)) + registry.addMapping("/**").allowedOriginPatterns( + allowedOrigins != null && !allowedOrigins.trim().isEmpty() ? Arrays.stream(allowedOrigins.split(",")) + .map(String::trim).filter(s -> !s.isEmpty()).toArray(String[]::new) : new String[0]) .allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS").allowedHeaders("*") .exposedHeaders("Authorization", "Jwttoken").allowCredentials(true).maxAge(3600); } diff --git a/src/main/java/com/iemr/common/identity/utils/CookieUtil.java b/src/main/java/com/iemr/common/identity/utils/CookieUtil.java index 8c196ee6..d5fefffd 100644 --- a/src/main/java/com/iemr/common/identity/utils/CookieUtil.java +++ b/src/main/java/com/iemr/common/identity/utils/CookieUtil.java @@ -12,9 +12,7 @@ @Service public class CookieUtil { - @Value("${isProduction}") - private Boolean isProduction; - + public Optional getCookieValue(HttpServletRequest request, String cookieName) { Cookie[] cookies = request.getCookies(); if (cookies != null) { @@ -38,11 +36,7 @@ public void addJwtTokenToCookie(String Jwttoken, HttpServletResponse response, H response.addCookie(cookie); } - public static String getJwtTokenFromCookie(HttpServletRequest request) { - if (request.getCookies() == null) { - return null; - } - return Arrays.stream(request.getCookies()).filter(cookie -> "Jwttoken".equalsIgnoreCase(cookie.getName())) - .map(Cookie::getValue).findFirst().orElse(null); + public String getJwtTokenFromCookie(HttpServletRequest request) { + return getCookieValue(request, "Jwttoken").orElse(null); } } diff --git a/src/main/java/com/iemr/common/identity/utils/JwtAuthenticationUtil.java b/src/main/java/com/iemr/common/identity/utils/JwtAuthenticationUtil.java index a157ede5..c3266a9b 100644 --- a/src/main/java/com/iemr/common/identity/utils/JwtAuthenticationUtil.java +++ b/src/main/java/com/iemr/common/identity/utils/JwtAuthenticationUtil.java @@ -1,5 +1,6 @@ package com.iemr.common.identity.utils; +import java.util.List; import java.util.Optional; import java.util.concurrent.TimeUnit; @@ -15,7 +16,6 @@ import com.iemr.common.identity.domain.User; import com.iemr.common.identity.exception.IEMRException; -import com.iemr.common.identity.repo.BenIdentityRepo; import io.jsonwebtoken.Claims; import jakarta.servlet.http.HttpServletRequest; @@ -28,8 +28,6 @@ public class JwtAuthenticationUtil { private JwtUtil jwtUtil; @Autowired private RedisTemplate redisTemplate; - @Autowired - private BenIdentityRepo benIdentityRepo; @Autowired private JdbcTemplate jdbcTemplate; @@ -105,21 +103,17 @@ private User getUserFromCache(String userId) { private User fetchUserFromDB(String userId) { String redisKey = "user_" + userId; // Redis key format - User user = jdbcTemplate.queryForObject( - "SELECT * FROM db_iemr.m_user WHERE UserID = ?", - new BeanPropertyRowMapper<>(User.class), - userId - ); - if (user != null) { - User userHash = new User(); - userHash.setUserID(user.getUserID()); - userHash.setUserName(user.getUserName()); - redisTemplate.opsForValue().set(redisKey, userHash, 30, TimeUnit.MINUTES); - logger.info("User stored in Redis with key: " + redisKey); - return user; - } else { + List users = jdbcTemplate.query("SELECT * FROM db_iemr.m_user WHERE UserID = ? AND Deleted = false", + new BeanPropertyRowMapper<>(User.class), userId); + + if (users.isEmpty()) { logger.warn("User not found for userId: " + userId); + return null; } - return null; + + User user = users.get(0); + redisTemplate.opsForValue().set(redisKey, user, 30, TimeUnit.MINUTES); + logger.info("User stored in Redis with key: " + redisKey); + return user; } } diff --git a/src/main/java/com/iemr/common/identity/utils/JwtUserIdValidationFilter.java b/src/main/java/com/iemr/common/identity/utils/JwtUserIdValidationFilter.java index aa6dbd79..4511d18f 100644 --- a/src/main/java/com/iemr/common/identity/utils/JwtUserIdValidationFilter.java +++ b/src/main/java/com/iemr/common/identity/utils/JwtUserIdValidationFilter.java @@ -148,6 +148,7 @@ private void clearUserIdCookie(HttpServletResponse response) { cookie.setHttpOnly(true); cookie.setSecure(true); cookie.setMaxAge(0); // Invalidate the cookie + cookie.setAttribute("SameSite", "Strict"); response.addCookie(cookie); } } diff --git a/src/main/java/com/iemr/common/identity/utils/JwtUtil.java b/src/main/java/com/iemr/common/identity/utils/JwtUtil.java index d15e08e7..cb5fc48b 100644 --- a/src/main/java/com/iemr/common/identity/utils/JwtUtil.java +++ b/src/main/java/com/iemr/common/identity/utils/JwtUtil.java @@ -10,7 +10,6 @@ import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; -import ch.qos.logback.classic.Logger; import io.jsonwebtoken.Claims; import io.jsonwebtoken.ExpiredJwtException; import io.jsonwebtoken.Jwts; @@ -70,17 +69,17 @@ public String generateRefreshToken(String username, String userId) { * @param expiration the expiration time of the token in milliseconds * @return the generated JWT token */ - private String buildToken(String username, String userId, String tokenType, long expiration) { - return Jwts.builder() - .subject(username) - .claim("userId", userId) - .claim("token_type", tokenType) - .id(UUID.randomUUID().toString()) - .issuedAt(new Date()) - .expiration(new Date(System.currentTimeMillis() + expiration)) - .signWith(getSigningKey()) - .compact(); - } + private String buildToken(String username, String userId, String tokenType, long expiration) { + if (username == null || username.trim().isEmpty()) { + throw new IllegalArgumentException("Username cannot be null or empty"); + } + if (userId == null || userId.trim().isEmpty()) { + throw new IllegalArgumentException("User ID cannot be null or empty"); + } + return Jwts.builder().subject(username).claim("userId", userId).claim("token_type", tokenType) + .id(UUID.randomUUID().toString()).issuedAt(new Date()) + .expiration(new Date(System.currentTimeMillis() + expiration)).signWith(getSigningKey()).compact(); + } /** * Validate the JWT token, checking if it is expired and if it's blacklisted @@ -110,14 +109,14 @@ public Claims validateToken(String token) { * @param token the JWT token * @return all claims from the token */ - public Claims getAllClaimsFromToken(String token) { - return Jwts.parser() - .verifyWith(getSigningKey()) - .build() - .parseSignedClaims(token) - .getPayload(); - - } + public Claims getAllClaimsFromToken(String token) { + Claims claims = validateToken(token); + if (claims == null) { + throw new IllegalArgumentException("Invalid or denylisted token"); + } + return claims; + + } /** * Extract a specific claim from the token using a function diff --git a/src/main/java/com/iemr/common/identity/utils/TokenDenylist.java b/src/main/java/com/iemr/common/identity/utils/TokenDenylist.java index a5b0fd09..ac4edfbf 100644 --- a/src/main/java/com/iemr/common/identity/utils/TokenDenylist.java +++ b/src/main/java/com/iemr/common/identity/utils/TokenDenylist.java @@ -7,6 +7,8 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.stereotype.Component; + +import com.iemr.common.identity.utils.exception.TokenDenylistException; @Component public class TokenDenylist { private final Logger logger = LoggerFactory.getLogger(this.getClass().getName()); @@ -20,20 +22,24 @@ private String getKey(String jti) { return PREFIX + jti; } - public void addTokenToDenylist(String jti, Long expirationTime) { - if (jti == null || jti.trim().isEmpty()) { - return; - } - if (expirationTime == null || expirationTime <= 0) { - throw new IllegalArgumentException("Expiration time must be positive"); - } - try { - String key = getKey(jti); // Use helper method to get the key - redisTemplate.opsForValue().set(key, " ", expirationTime, TimeUnit.MILLISECONDS); - } catch (Exception e) { - throw new RuntimeException("Failed to denylist token", e); - } - } + public void addTokenToDenylist(String jti, Long expirationTime) { + if (jti == null || jti.trim().isEmpty()) { + logger.warn("Attempted to add null or empty jti to denylist"); + return; + } + if (expirationTime == null || expirationTime <= 0) { + logger.error("Invalid expiration time for jti: {}", jti); + throw new IllegalArgumentException("Expiration time must be positive"); + } + try { + String key = getKey(jti); // Use helper method to get the key + redisTemplate.opsForValue().set(key, " ", expirationTime, TimeUnit.MILLISECONDS); + logger.debug("Added jti to denylist: {}", jti); + } catch (Exception e) { + logger.error("Failed to denylist token with jti: {}", jti, e); + throw new TokenDenylistException("Failed to denylist token", e); + } + } public boolean isTokenDenylisted(String jti) { if (jti == null || jti.trim().isEmpty()) { @@ -44,7 +50,7 @@ public boolean isTokenDenylisted(String jti) { return Boolean.TRUE.equals(redisTemplate.hasKey(key)); } catch (Exception e) { logger.error("Failed to check denylist status for jti: " + jti, e); - return false; + throw new TokenDenylistException("Unable to verify token denylist status", e); } } diff --git a/src/main/java/com/iemr/common/identity/utils/UserAgentContext.java b/src/main/java/com/iemr/common/identity/utils/UserAgentContext.java index 2fd2ef21..59cc804b 100644 --- a/src/main/java/com/iemr/common/identity/utils/UserAgentContext.java +++ b/src/main/java/com/iemr/common/identity/utils/UserAgentContext.java @@ -3,9 +3,12 @@ public class UserAgentContext { private static final ThreadLocal userAgentHolder = new ThreadLocal<>(); - public static void setUserAgent(String userAgent) { - userAgentHolder.set(userAgent); - } + public static void setUserAgent(String userAgent) { + if (userAgent != null && userAgent.trim().isEmpty()) { + userAgent = null; // Treat empty strings as null + } + userAgentHolder.set(userAgent); + } public static String getUserAgent() { return userAgentHolder.get(); diff --git a/src/main/java/com/iemr/common/identity/utils/exception/TokenDenylistException.java b/src/main/java/com/iemr/common/identity/utils/exception/TokenDenylistException.java new file mode 100644 index 00000000..a8e6e828 --- /dev/null +++ b/src/main/java/com/iemr/common/identity/utils/exception/TokenDenylistException.java @@ -0,0 +1,7 @@ +package com.iemr.common.identity.utils.exception; + +public class TokenDenylistException extends RuntimeException { + public TokenDenylistException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/src/main/java/com/iemr/common/identity/utils/http/AuthorizationHeaderRequestWrapper.java b/src/main/java/com/iemr/common/identity/utils/http/AuthorizationHeaderRequestWrapper.java index d748de2a..bbbc3de7 100644 --- a/src/main/java/com/iemr/common/identity/utils/http/AuthorizationHeaderRequestWrapper.java +++ b/src/main/java/com/iemr/common/identity/utils/http/AuthorizationHeaderRequestWrapper.java @@ -12,7 +12,7 @@ public class AuthorizationHeaderRequestWrapper extends HttpServletRequestWrapper public AuthorizationHeaderRequestWrapper(HttpServletRequest request, String authHeaderValue) { super(request); - this.Authorization = authHeaderValue; + this.Authorization = authHeaderValue != null ? authHeaderValue : ""; } @Override @@ -26,7 +26,8 @@ public String getHeader(String name) { @Override public Enumeration getHeaders(String name) { if ("Authorization".equalsIgnoreCase(name)) { - return Collections.enumeration(Collections.singletonList(Authorization)); + return Authorization != null ? Collections.enumeration(Collections.singletonList(Authorization)) + : Collections.emptyEnumeration(); } return super.getHeaders(name); } @@ -34,7 +35,8 @@ public Enumeration getHeaders(String name) { @Override public Enumeration getHeaderNames() { List names = Collections.list(super.getHeaderNames()); - if (!names.contains("Authorization")) { + boolean hasAuth = names.stream().anyMatch(name -> "Authorization".equalsIgnoreCase(name)); + if (!hasAuth) { names.add("Authorization"); } return Collections.enumeration(names); diff --git a/src/main/java/com/iemr/common/identity/utils/redis/RedisConfig.java b/src/main/java/com/iemr/common/identity/utils/redis/RedisConfig.java index d1ad56ac..b918a869 100644 --- a/src/main/java/com/iemr/common/identity/utils/redis/RedisConfig.java +++ b/src/main/java/com/iemr/common/identity/utils/redis/RedisConfig.java @@ -8,6 +8,8 @@ import org.springframework.data.redis.serializer.StringRedisSerializer; import org.springframework.session.data.redis.config.ConfigureRedisAction; +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.ObjectMapper; import com.iemr.common.identity.domain.User; @Configuration @@ -27,7 +29,13 @@ public RedisTemplate redisTemplate(RedisConnectionFactory factor // Use Jackson2JsonRedisSerializer for values (Users objects) Jackson2JsonRedisSerializer serializer = new Jackson2JsonRedisSerializer<>(User.class); + ObjectMapper objectMapper = new ObjectMapper(); + objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); + objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL); + serializer.setObjectMapper(objectMapper); template.setValueSerializer(serializer); + template.setHashKeySerializer(new StringRedisSerializer()); + template.setHashValueSerializer(serializer); return template; } From f484251448656afaa12db48f670a9ddbc8449239 Mon Sep 17 00:00:00 2001 From: Ravi Shanigarapu Date: Wed, 30 Jul 2025 14:10:08 +0530 Subject: [PATCH 09/14] coderabbitai comments addressed --- .../utils/JwtUserIdValidationFilter.java | 14 ++++--------- .../common/identity/utils/TokenDenylist.java | 20 +++++++++++++------ 2 files changed, 18 insertions(+), 16 deletions(-) diff --git a/src/main/java/com/iemr/common/identity/utils/JwtUserIdValidationFilter.java b/src/main/java/com/iemr/common/identity/utils/JwtUserIdValidationFilter.java index 4511d18f..2d82ea69 100644 --- a/src/main/java/com/iemr/common/identity/utils/JwtUserIdValidationFilter.java +++ b/src/main/java/com/iemr/common/identity/utils/JwtUserIdValidationFilter.java @@ -19,12 +19,14 @@ public class JwtUserIdValidationFilter implements Filter { private final JwtAuthenticationUtil jwtAuthenticationUtil; + private final CookieUtil cookieUtil; private final Logger logger = LoggerFactory.getLogger(this.getClass().getName()); private final String allowedOrigins; - public JwtUserIdValidationFilter(JwtAuthenticationUtil jwtAuthenticationUtil, String allowedOrigins) { + public JwtUserIdValidationFilter(JwtAuthenticationUtil jwtAuthenticationUtil, String allowedOrigins, CookieUtil cookieUtil) { this.jwtAuthenticationUtil = jwtAuthenticationUtil; this.allowedOrigins = allowedOrigins; + this.cookieUtil = cookieUtil; } @Override @@ -131,15 +133,7 @@ private boolean isMobileClient(String userAgent) { } private String getJwtTokenFromCookies(HttpServletRequest request) { - Cookie[] cookies = request.getCookies(); - if (cookies != null) { - for (Cookie cookie : cookies) { - if (cookie.getName().equalsIgnoreCase("Jwttoken")) { - return cookie.getValue(); - } - } - } - return null; + return cookieUtil.getJwtTokenFromCookie(request); } private void clearUserIdCookie(HttpServletResponse response) { diff --git a/src/main/java/com/iemr/common/identity/utils/TokenDenylist.java b/src/main/java/com/iemr/common/identity/utils/TokenDenylist.java index ac4edfbf..1248f478 100644 --- a/src/main/java/com/iemr/common/identity/utils/TokenDenylist.java +++ b/src/main/java/com/iemr/common/identity/utils/TokenDenylist.java @@ -55,10 +55,18 @@ public boolean isTokenDenylisted(String jti) { } // Remove a token's jti from the denylist (Redis) - public void removeTokenFromDenylist(String jti) { - if (jti != null && !jti.trim().isEmpty()) { - String key = getKey(jti); - redisTemplate.delete(key); - } - } + public void removeTokenFromDenylist(String jti) { + if (jti != null && !jti.trim().isEmpty()) { + try { + String key = getKey(jti); + redisTemplate.delete(key); + logger.debug("Removed jti from denylist: {}", jti); + } catch (Exception e) { + logger.error("Failed to remove token from denylist with jti: {}", jti, e); + throw new TokenDenylistException("Failed to remove token from denylist", e); + } + } else { + logger.warn("Attempted to remove null or empty jti from denylist"); + } + } } From f2161c865b1b2aa629fffea769d9b84d3d4fef04 Mon Sep 17 00:00:00 2001 From: Ravi Shanigarapu Date: Wed, 30 Jul 2025 14:13:09 +0530 Subject: [PATCH 10/14] coderabbit commments addressed --- .../java/com/iemr/common/identity/utils/FilterConfig.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/iemr/common/identity/utils/FilterConfig.java b/src/main/java/com/iemr/common/identity/utils/FilterConfig.java index 0a8b0904..e13b8b4e 100644 --- a/src/main/java/com/iemr/common/identity/utils/FilterConfig.java +++ b/src/main/java/com/iemr/common/identity/utils/FilterConfig.java @@ -13,10 +13,10 @@ public class FilterConfig { @Bean public FilterRegistrationBean jwtUserIdValidationFilter( - JwtAuthenticationUtil jwtAuthenticationUtil) { + JwtAuthenticationUtil jwtAuthenticationUtil, CookieUtil cookieUtil) { FilterRegistrationBean registrationBean = new FilterRegistrationBean<>(); - JwtUserIdValidationFilter filter = new JwtUserIdValidationFilter(jwtAuthenticationUtil, allowedOrigins); + JwtUserIdValidationFilter filter = new JwtUserIdValidationFilter(jwtAuthenticationUtil, allowedOrigins, cookieUtil); registrationBean.setFilter(filter); registrationBean.setOrder(Ordered.HIGHEST_PRECEDENCE); registrationBean.addUrlPatterns("/*"); // Apply filter to all API endpoints From 635263c97ef14c15b6c69dc8156fe04845ea9498 Mon Sep 17 00:00:00 2001 From: Ravi Shanigarapu Date: Wed, 30 Jul 2025 14:26:48 +0530 Subject: [PATCH 11/14] code optimised --- .../utils/JwtUserIdValidationFilter.java | 34 +++++++++++-------- 1 file changed, 19 insertions(+), 15 deletions(-) diff --git a/src/main/java/com/iemr/common/identity/utils/JwtUserIdValidationFilter.java b/src/main/java/com/iemr/common/identity/utils/JwtUserIdValidationFilter.java index 2d82ea69..3ef225dd 100644 --- a/src/main/java/com/iemr/common/identity/utils/JwtUserIdValidationFilter.java +++ b/src/main/java/com/iemr/common/identity/utils/JwtUserIdValidationFilter.java @@ -35,26 +35,13 @@ public void doFilter(ServletRequest servletRequest, ServletResponse servletRespo HttpServletRequest request = (HttpServletRequest) servletRequest; HttpServletResponse response = (HttpServletResponse) servletResponse; - String origin = request.getHeader("Origin"); - - logger.debug("Incoming Origin: {}", origin); - logger.debug("Allowed Origins Configured: {}", allowedOrigins); - - if (origin != null && isOriginAllowed(origin)) { - response.setHeader("Access-Control-Allow-Origin", origin); - response.setHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS"); - response.setHeader("Access-Control-Allow-Headers", "Authorization, Content-Type, Accept, Jwttoken"); - response.setHeader("Access-Control-Allow-Credentials", "true"); - } else { - logger.warn("Origin [{}] is NOT allowed. CORS headers NOT added.", origin); - } - + handleCorsHeaders(request, response); + if ("OPTIONS".equalsIgnoreCase(request.getMethod())) { logger.info("OPTIONS request - skipping JWT validation"); response.setStatus(HttpServletResponse.SC_OK); return; } - String path = request.getRequestURI(); logger.info("JwtUserIdValidationFilter invoked for path: " + path); @@ -110,6 +97,23 @@ public void doFilter(ServletRequest servletRequest, ServletResponse servletRespo } } + private void handleCorsHeaders(HttpServletRequest request, HttpServletResponse response) { + String origin = request.getHeader("Origin"); + + logger.debug("Incoming Origin: {}", origin); + logger.debug("Allowed Origins Configured: {}", allowedOrigins); + + if (origin != null && isOriginAllowed(origin)) { + response.setHeader("Access-Control-Allow-Origin", origin); + response.setHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS"); + response.setHeader("Access-Control-Allow-Headers", "Authorization, Content-Type, Accept, Jwttoken"); + response.setHeader("Access-Control-Allow-Credentials", "true"); + } else { + logger.warn("Origin [{}] is NOT allowed. CORS headers NOT added.", origin); + } + + } + private boolean isOriginAllowed(String origin) { if (origin == null || allowedOrigins == null || allowedOrigins.trim().isEmpty()) { logger.warn("No allowed origins configured or origin is null"); From 8fe74d99346f6aaf6e2ac9eb98f724dd5deeeb0b Mon Sep 17 00:00:00 2001 From: Ravi Shanigarapu Date: Wed, 30 Jul 2025 15:18:12 +0530 Subject: [PATCH 12/14] redis runtime issuefixed --- .../identity/utils/redis/RedisConfig.java | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/src/main/java/com/iemr/common/identity/utils/redis/RedisConfig.java b/src/main/java/com/iemr/common/identity/utils/redis/RedisConfig.java index b918a869..eb235f31 100644 --- a/src/main/java/com/iemr/common/identity/utils/redis/RedisConfig.java +++ b/src/main/java/com/iemr/common/identity/utils/redis/RedisConfig.java @@ -8,8 +8,9 @@ import org.springframework.data.redis.serializer.StringRedisSerializer; import org.springframework.session.data.redis.config.ConfigureRedisAction; -import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.SerializationFeature; +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; import com.iemr.common.identity.domain.User; @Configuration @@ -24,18 +25,12 @@ public RedisTemplate redisTemplate(RedisConnectionFactory factor RedisTemplate template = new RedisTemplate<>(); template.setConnectionFactory(factory); - // Use StringRedisSerializer for keys (userId) - template.setKeySerializer(new StringRedisSerializer()); - - // Use Jackson2JsonRedisSerializer for values (Users objects) Jackson2JsonRedisSerializer serializer = new Jackson2JsonRedisSerializer<>(User.class); - ObjectMapper objectMapper = new ObjectMapper(); - objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); - objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL); - serializer.setObjectMapper(objectMapper); + ObjectMapper mapper = new ObjectMapper(); + mapper.registerModule(new JavaTimeModule()); + mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS); + serializer.setObjectMapper(mapper); template.setValueSerializer(serializer); - template.setHashKeySerializer(new StringRedisSerializer()); - template.setHashValueSerializer(serializer); return template; } From 1439253df2eec2771f00fe4d39c447b3490e1d5c Mon Sep 17 00:00:00 2001 From: Ravi Shanigarapu Date: Thu, 31 Jul 2025 10:39:45 +0530 Subject: [PATCH 13/14] Coderabbit changes --- .../iemr/common/identity/utils/JwtUserIdValidationFilter.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/iemr/common/identity/utils/JwtUserIdValidationFilter.java b/src/main/java/com/iemr/common/identity/utils/JwtUserIdValidationFilter.java index 3ef225dd..a030ab31 100644 --- a/src/main/java/com/iemr/common/identity/utils/JwtUserIdValidationFilter.java +++ b/src/main/java/com/iemr/common/identity/utils/JwtUserIdValidationFilter.java @@ -106,7 +106,9 @@ private void handleCorsHeaders(HttpServletRequest request, HttpServletResponse r if (origin != null && isOriginAllowed(origin)) { response.setHeader("Access-Control-Allow-Origin", origin); response.setHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS"); - response.setHeader("Access-Control-Allow-Headers", "Authorization, Content-Type, Accept, Jwttoken"); + response.setHeader("Access-Control-Allow-Headers", + "Authorization, Content-Type, Accept, JwtToken, Jwttoken"); + response.setHeader("Vary", "Origin"); response.setHeader("Access-Control-Allow-Credentials", "true"); } else { logger.warn("Origin [{}] is NOT allowed. CORS headers NOT added.", origin); From ca3a4659df4ec09adf304bd7cba4eb5436ebb2d3 Mon Sep 17 00:00:00 2001 From: Ravi Shanigarapu Date: Thu, 31 Jul 2025 11:17:22 +0530 Subject: [PATCH 14/14] removed unused method --- .../com/iemr/common/identity/utils/CookieUtil.java | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/src/main/java/com/iemr/common/identity/utils/CookieUtil.java b/src/main/java/com/iemr/common/identity/utils/CookieUtil.java index d5fefffd..84f54526 100644 --- a/src/main/java/com/iemr/common/identity/utils/CookieUtil.java +++ b/src/main/java/com/iemr/common/identity/utils/CookieUtil.java @@ -1,14 +1,11 @@ package com.iemr.common.identity.utils; -import java.util.Arrays; import java.util.Optional; -import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; import jakarta.servlet.http.Cookie; import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; @Service public class CookieUtil { @@ -25,17 +22,6 @@ public Optional getCookieValue(HttpServletRequest request, String cookie return Optional.empty(); } - public void addJwtTokenToCookie(String Jwttoken, HttpServletResponse response, HttpServletRequest request) { - Cookie cookie = new Cookie("Jwttoken", Jwttoken); - cookie.setHttpOnly(true); - cookie.setMaxAge(60 * 60 * 24); - cookie.setPath("/"); - if ("https".equalsIgnoreCase(request.getScheme())) { - cookie.setSecure(true); - } - response.addCookie(cookie); - } - public String getJwtTokenFromCookie(HttpServletRequest request) { return getCookieValue(request, "Jwttoken").orElse(null); }