diff --git a/pom.xml b/pom.xml index 7a7457ba..f29e4dca 100644 --- a/pom.xml +++ b/pom.xml @@ -66,10 +66,10 @@ - - co.elastic.logging - logback-ecs-encoder - 1.3.2 + + co.elastic.logging + logback-ecs-encoder + 1.3.2 @@ -172,28 +172,11 @@ - - org.json json 20231013 - @@ -201,16 +184,16 @@ quartz 2.5.0-rc1 - - com.fasterxml.jackson.core - jackson-annotations - 2.17.0 - - - com.fasterxml.jackson.core - jackson-core - 2.17.0 - + + com.fasterxml.jackson.core + jackson-annotations + 2.17.0 + + + com.fasterxml.jackson.core + jackson-core + 2.17.0 + com.openkm sdk4j @@ -423,7 +406,7 @@ org.apache.xmlgraphics batik-all - + org.bouncycastle bcpkix-jdk15on @@ -440,7 +423,7 @@ org.apache.santuario xmlsec - + commons-io commons-io @@ -457,45 +440,64 @@ spring-web 6.1.6 - - - javax.ws.rs - javax.ws.rs-api - 2.1.1 - - - - - org.glassfish.jersey.core - jersey-common - 2.30.1 - - - org.glassfish.jersey.core - jersey-server - 2.30.1 - - - - - - org.glassfish.jersey.media - jersey-media-json-processing - 2.30.1 - - - - - org.glassfish.jaxb - jaxb-runtime - 2.3.1 - + + javax.ws.rs + javax.ws.rs-api + 2.1.1 + + + + + org.glassfish.jersey.core + jersey-common + 2.30.1 + + + org.glassfish.jersey.core + jersey-server + 2.30.1 + + + + + + org.glassfish.jersey.media + jersey-media-json-processing + 2.30.1 + + + + + org.glassfish.jaxb + jaxb-runtime + 2.3.1 + + + + io.jsonwebtoken + jjwt-api + 0.12.6 + + + + io.jsonwebtoken + jjwt-impl + 0.12.6 + runtime + + + + io.jsonwebtoken + jjwt-jackson + 0.12.6 + runtime + - commonapi-v1.0 + commonapi-v3.0.0 org.apache.maven.plugins @@ -560,8 +562,7 @@ ${target-properties} and ${source-properties} - diff --git a/src/main/environment/common_ci.properties b/src/main/environment/common_ci.properties index 86cc945c..709c5156 100644 --- a/src/main/environment/common_ci.properties +++ b/src/main/environment/common_ci.properties @@ -140,9 +140,13 @@ eausadhaAuthorization= spring.main.allow-bean-definition-overriding=true spring.main.allow-circular-references=true +jwt.secret=@env.JWT_SECRET_KEY@ + + #ELK logging file name logging.file.name=@env.COMMON_API_LOGGING_FILE_NAME@ + ##grievance API call updateGrievanceDetails = @env.GRIEVANCE_API_BASE_URL@/grsbepro/igemr1097/public/api/v1/state-wise/grievance-list updateGrievanceTransactionDetails=@env.GRIEVANCE_API_BASE_URL@/grsbepro/igemr1097/public/api/v1/grievance_details/ @@ -152,3 +156,4 @@ grievanceUserName = @env.GRIEVANCE_USERNAME@ grievancePassword = @env.GRIEVANCE_PASSWORD@ grievanceUserAuthenticate = @env.GRIEVANCE_USER_AUTHENTICATE@ grievanceDataSyncDuration = @env.GRIEVANCE_DATA_SYNC_DURATION@ + diff --git a/src/main/environment/common_dev.properties b/src/main/environment/common_dev.properties index 0ee3e6cd..2331c878 100644 --- a/src/main/environment/common_dev.properties +++ b/src/main/environment/common_dev.properties @@ -170,6 +170,7 @@ spring.main.allow-bean-definition-overriding=true spring.main.allow-circular-references=true fileBasePath =/Doc +jwt.secret= ##grievance API call updateGrievanceDetails = /grsbepro/igemr1097/public/api/v1/state-wise/grievance-list diff --git a/src/main/environment/common_example.properties b/src/main/environment/common_example.properties index 1f1e272b..c72d4d41 100644 --- a/src/main/environment/common_example.properties +++ b/src/main/environment/common_example.properties @@ -114,6 +114,8 @@ eAusadhaUrl=https://dlc.kar.nic.in/e-services/api/DWInstituteInward eausadhaAuthorization= spring.main.allow-bean-definition-overriding=true spring.main.allow-circular-references=true +jwt.secret= + ##grievance API call updateGrievanceDetails = /grsbepro/igemr1097/public/api/v1/state-wise/grievance-list @@ -125,3 +127,4 @@ grievancePassword = grievanceUserAuthenticate = grievanceDataSyncDuration = + diff --git a/src/main/environment/common_test.properties b/src/main/environment/common_test.properties index 37c898e2..f9248624 100644 --- a/src/main/environment/common_test.properties +++ b/src/main/environment/common_test.properties @@ -171,6 +171,8 @@ spring.main.allow-bean-definition-overriding=true spring.main.allow-circular-references=true fileBasePath =/Doc +jwt.secret= + ##grievance API call updateGrievanceDetails = /grsbepro/igemr1097/public/api/v1/state-wise/grievance-list diff --git a/src/main/environment/common_uat.properties b/src/main/environment/common_uat.properties index bb8b53de..a6702938 100644 --- a/src/main/environment/common_uat.properties +++ b/src/main/environment/common_uat.properties @@ -143,6 +143,7 @@ spring.main.allow-bean-definition-overriding=true spring.main.allow-circular-references=true fileBasePath =/Doc +jwt.secret= ##grievance API call updateGrievanceDetails = /grsbepro/igemr1097/public/api/v1/state-wise/grievance-list diff --git a/src/main/java/com/iemr/common/CommonApplication.java b/src/main/java/com/iemr/common/CommonApplication.java index b1e03d41..83078018 100644 --- a/src/main/java/com/iemr/common/CommonApplication.java +++ b/src/main/java/com/iemr/common/CommonApplication.java @@ -22,14 +22,18 @@ package com.iemr.common; import org.springframework.boot.SpringApplication; -import org.springframework.boot.autoconfigure.AutoConfiguration; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.builder.SpringApplicationBuilder; import org.springframework.boot.web.servlet.support.SpringBootServletInitializer; import org.springframework.context.annotation.Bean; +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.scheduling.annotation.EnableScheduling; import org.springframework.web.client.RestTemplate; +import com.iemr.common.data.users.User; import com.iemr.common.utils.IEMRApplBeans; @SpringBootApplication @@ -41,11 +45,11 @@ public IEMRApplBeans instantiateBeans() { return new IEMRApplBeans(); } - @Bean - public RestTemplate restTemplate() { - return new RestTemplate(); - } - + @Bean + public RestTemplate restTemplate() { + return new RestTemplate(); + } + public static void main(String[] args) { SpringApplication.run(CommonApplication.class, args); } @@ -54,4 +58,19 @@ protected SpringApplicationBuilder configure(SpringApplicationBuilder applicatio return application.sources(new Class[] { CommonApplication.class }); } + @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/java/com/iemr/common/config/RedisConfig.java b/src/main/java/com/iemr/common/config/RedisConfig.java index ae1519e0..faac71ae 100644 --- a/src/main/java/com/iemr/common/config/RedisConfig.java +++ b/src/main/java/com/iemr/common/config/RedisConfig.java @@ -23,14 +23,35 @@ 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.data.users.User; + @Configuration public class RedisConfig { - + + @Bean + public ConfigureRedisAction configureRedisAction() { + return ConfigureRedisAction.NO_OP; + } + @Bean - public ConfigureRedisAction configureRedisAction() { - return ConfigureRedisAction.NO_OP; - } + 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/java/com/iemr/common/controller/users/IEMRAdminController.java b/src/main/java/com/iemr/common/controller/users/IEMRAdminController.java index 317bdf6a..0c0aaac7 100644 --- a/src/main/java/com/iemr/common/controller/users/IEMRAdminController.java +++ b/src/main/java/com/iemr/common/controller/users/IEMRAdminController.java @@ -28,7 +28,7 @@ import java.util.Iterator; import java.util.List; import java.util.Map; - +import java.util.concurrent.TimeUnit; import javax.ws.rs.core.MediaType; @@ -37,6 +37,9 @@ 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.web.bind.annotation.CrossOrigin; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; @@ -62,6 +65,9 @@ import com.iemr.common.model.user.LoginRequestModel; import com.iemr.common.model.user.LoginResponseModel; import com.iemr.common.service.users.IEMRAdminUserService; +import com.iemr.common.utils.CookieUtil; +import com.iemr.common.utils.JwtAuthenticationUtil; +import com.iemr.common.utils.JwtUtil; import com.iemr.common.utils.encryption.AESUtil; import com.iemr.common.utils.exception.IEMRException; import com.iemr.common.utils.mapper.InputMapper; @@ -72,7 +78,9 @@ import io.lettuce.core.dynamic.annotation.Param; import io.swagger.v3.oas.annotations.Operation; +import jakarta.servlet.http.Cookie; import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; @RequestMapping("/user") @RestController @@ -81,15 +89,22 @@ public class IEMRAdminController { private InputMapper inputMapper = new InputMapper(); private IEMRAdminUserService iemrAdminUserServiceImpl; - + @Autowired + private JwtUtil jwtUtil; + @Autowired + private CookieUtil cookieUtil; + @Autowired + private JwtAuthenticationUtil jwtAuthenticationUtil; + @Autowired + private RedisTemplate redisTemplate; + private AESUtil aesUtil; @Autowired public void setAesUtil(AESUtil aesUtil) { - this.aesUtil = aesUtil; + this.aesUtil = aesUtil; } - @Autowired public void setIemrAdminUserService(IEMRAdminUserService iemrAdminUserService) { this.iemrAdminUserServiceImpl = iemrAdminUserService; @@ -125,7 +140,7 @@ public String userAuthenticateNew( @RequestMapping(value = "/userAuthenticate", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON) public String userAuthenticate( @Param(value = "\"{\\\"userName\\\":\\\"String\\\",\\\"password\\\":\\\"String\\\"}\"") @RequestBody LoginRequestModel m_User, - HttpServletRequest request) { + HttpServletRequest request, HttpServletResponse httpResponse) { OutputResponse response = new OutputResponse(); logger.info("userAuthenticate request - " + m_User + " " + m_User.getUserName() + " " + m_User.getPassword()); try { @@ -137,7 +152,8 @@ public String userAuthenticate( JSONObject serviceRoleMap = new JSONObject(); JSONArray serviceRoleList = new JSONArray(); JSONObject previlegeObj = new JSONObject(); - if (m_User.getUserName() != null && (m_User.getDoLogout() == null || m_User.getDoLogout() == false)) { + if (m_User.getUserName() != null && (m_User.getDoLogout() == null || m_User.getDoLogout() == false) + && (m_User.getWithCredentials() != null || m_User.getWithCredentials() == true)) { String tokenFromRedis = getConcurrentCheckSessionObjectAgainstUser( m_User.getUserName().trim().toLowerCase()); if (tokenFromRedis != null) { @@ -148,6 +164,21 @@ public String userAuthenticate( deleteSessionObject(m_User.getUserName().trim().toLowerCase()); } if (mUser.size() == 1) { + String Jwttoken = jwtUtil.generateToken(m_User.getUserName(), mUser.get(0).getUserID().toString()); + logger.info("jwt token is:" + Jwttoken); + + User user = new User(); // Assuming the Users class exists + user.setUserID(mUser.get(0).getUserID()); + user.setUserName(mUser.get(0).getUserName()); + + String redisKey = "user_" + mUser.get(0).getUserID(); // Use user ID to create a unique key + + // Store the user in Redis (set a TTL of 30 minutes) + redisTemplate.opsForValue().set(redisKey, user, 30, TimeUnit.MINUTES); + + // Set Jwttoken in the response cookie + cookieUtil.addJwtTokenToCookie(Jwttoken, httpResponse, request); + createUserMapping(mUser.get(0), resMap, serviceRoleMultiMap, serviceRoleMap, serviceRoleList, previlegeObj); } else { @@ -269,11 +300,11 @@ private void createUserMapping(User mUser, JSONObject resMap, JSONObject service previlegeObj.getJSONObject(serv).put("agentID", m_UserServiceRoleMapping.getAgentID()); previlegeObj.getJSONObject(serv).put("agentPassword", m_UserServiceRoleMapping.getAgentPassword()); } - JSONArray roles = previlegeObj.getJSONObject(serv).getJSONArray("roles"); + JSONArray roles = previlegeObj.getJSONObject(serv).getJSONArray("roles"); // roles.put(new JSONObject(m_UserServiceRoleMapping.getM_Role().toString())); - JSONObject roleObject = new JSONObject(m_UserServiceRoleMapping.getM_Role().toString()); - roleObject.put("isSanjeevani", m_UserServiceRoleMapping.getIsSanjeevani()); - roles.put(roleObject); + JSONObject roleObject = new JSONObject(m_UserServiceRoleMapping.getM_Role().toString()); + roleObject.put("isSanjeevani", m_UserServiceRoleMapping.getIsSanjeevani()); + roles.put(roleObject); } Iterator keySet = serviceRoleMultiMap.keys(); while (keySet.hasNext()) { @@ -591,8 +622,9 @@ public String getRoleScreenMappingByProviderID( logger.info("getRoleScreenMappingByProviderID"); try { ObjectMapper objectMapper = new ObjectMapper(); - ServiceRoleScreenMapping serviceRoleScreenMapping = objectMapper.readValue(request, ServiceRoleScreenMapping.class); - + ServiceRoleScreenMapping serviceRoleScreenMapping = objectMapper.readValue(request, + ServiceRoleScreenMapping.class); + List mapping = iemrAdminUserServiceImpl .getUserServiceRoleMappingForProvider(serviceRoleScreenMapping.getProviderServiceMapID()); @@ -611,10 +643,9 @@ public String getRoleScreenMappingByProviderID( */) @Operation(summary = "Get users by provider id") @RequestMapping(value = "/getUsersByProviderID", method = RequestMethod.POST, consumes = MediaType.APPLICATION_JSON, produces = MediaType.APPLICATION_JSON, headers = "Authorization") - public String getUsersByProviderID( - @Param(value = "{\"providerServiceMapID\":\"Integer - providerServiceMapID\", " - + "\"RoleID\":\"Optional: Integer - role ID to be filtered\", " - + "\"languageName\":\"Optional: String - languageName\"}") @RequestBody String request) { + public String getUsersByProviderID(@Param(value = "{\"providerServiceMapID\":\"Integer - providerServiceMapID\", " + + "\"RoleID\":\"Optional: Integer - role ID to be filtered\", " + + "\"languageName\":\"Optional: String - languageName\"}") @RequestBody String request) { OutputResponse response = new OutputResponse(); logger.info("getRolesByProviderID request "); try { @@ -887,8 +918,9 @@ public String userAuthenticateBhavya( OutputResponse response = new OutputResponse(); logger.info("userAuthenticate request - " + m_User + " " + m_User.getUserName() + " " + m_User.getPassword()); try { - //String decryptPassword = aesUtil.decrypt("Piramal12Piramal", m_User.getPassword()); - //logger.info("decryptPassword : " + m_User.getPassword()); + // String decryptPassword = aesUtil.decrypt("Piramal12Piramal", + // m_User.getPassword()); + // logger.info("decryptPassword : " + m_User.getPassword()); List mUser = iemrAdminUserServiceImpl.userAuthenticate(m_User.getUserName(), m_User.getPassword()); JSONObject resMap = new JSONObject(); JSONObject serviceRoleMultiMap = new JSONObject(); @@ -934,4 +966,22 @@ public String userAuthenticateBhavya( return response.toString(); } + @GetMapping("/get-jwt-token") + public ResponseEntity getJwtTokenFromCookie(HttpServletRequest httpRequest) { + // Retrieve the cookie named 'jwtToken' + Cookie[] cookies = httpRequest.getCookies(); + + if (cookies != null) { + for (Cookie cookie : cookies) { + if ("jwtToken".equals(cookie.getName())) { + String jwtToken = cookie.getValue(); + // Return the JWT token in the response + return ResponseEntity.ok(jwtToken); + } + } + } + // Return 404 if the token is not found in the cookies + return ResponseEntity.status(HttpStatus.NOT_FOUND).body("JWT token not found"); + } + } diff --git a/src/main/java/com/iemr/common/data/users/User.java b/src/main/java/com/iemr/common/data/users/User.java index d4030347..4710b11d 100644 --- a/src/main/java/com/iemr/common/data/users/User.java +++ b/src/main/java/com/iemr/common/data/users/User.java @@ -21,11 +21,13 @@ */ package com.iemr.common.data.users; +import java.io.Serializable; import java.sql.Timestamp; import java.util.List; import java.util.Set; import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.google.gson.annotations.Expose; import com.iemr.common.data.callhandling.OutboundCallRequest; import com.iemr.common.data.feedback.FeedbackDetails; @@ -52,7 +54,7 @@ @Entity @Table(name = "m_user") @Data -public class User { +public class User implements Serializable { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @Expose @@ -194,6 +196,7 @@ public class User { private String newPassword = null; @Transient + @JsonIgnore private OutputMapper outPutMapper = new OutputMapper(); // new field for rate-limit, failed authentication diff --git a/src/main/java/com/iemr/common/model/user/LoginRequestModel.java b/src/main/java/com/iemr/common/model/user/LoginRequestModel.java index 770eaeb4..81308b49 100644 --- a/src/main/java/com/iemr/common/model/user/LoginRequestModel.java +++ b/src/main/java/com/iemr/common/model/user/LoginRequestModel.java @@ -25,23 +25,21 @@ import lombok.EqualsAndHashCode; @EqualsAndHashCode(exclude = { "authKey" }) -public @Data class LoginRequestModel -{ +public @Data class LoginRequestModel { private String userName; private String password; private String authKey; private Boolean doLogout; - - - + private Boolean withCredentials; + public LoginRequestModel() { - + } - //everwell-1097 registration + + // everwell-1097 registration public LoginRequestModel(String userName, String password) { this.userName = userName; this.password = password; } - - + } diff --git a/src/main/java/com/iemr/common/service/users/IEMRAdminUserServiceImpl.java b/src/main/java/com/iemr/common/service/users/IEMRAdminUserServiceImpl.java index ff58fef0..a9a7858d 100644 --- a/src/main/java/com/iemr/common/service/users/IEMRAdminUserServiceImpl.java +++ b/src/main/java/com/iemr/common/service/users/IEMRAdminUserServiceImpl.java @@ -1171,4 +1171,22 @@ private String toHex(byte[] array) throws NoSuchAlgorithmException { } return hex; } + + public User getUserById(Long userId) throws IEMRException { + try { + // Fetch user from custom repository by userId + User user = iEMRUserRepositoryCustom.findByUserID(userId); + + // Check if user is found + if (user == null) { + throw new IEMRException("User not found with ID: " + userId); + } + + return user; + } catch (Exception e) { + // Log and throw custom exception in case of errors + logger.error("Error fetching user with ID: " + userId, e); + throw new IEMRException("Error fetching user with ID: " + userId, e); + } + } } diff --git a/src/main/java/com/iemr/common/utils/CookieUtil.java b/src/main/java/com/iemr/common/utils/CookieUtil.java new file mode 100644 index 00000000..7fb103f5 --- /dev/null +++ b/src/main/java/com/iemr/common/utils/CookieUtil.java @@ -0,0 +1,43 @@ +package com.iemr.common.utils; + +import java.util.Arrays; +import java.util.Optional; + +import org.springframework.stereotype.Service; + +import jakarta.servlet.http.Cookie; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; + +@Service +public class CookieUtil { + + 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) { + // Create a new cookie with the JWT token + Cookie cookie = new Cookie("Jwttoken", Jwttoken); + cookie.setHttpOnly(true); // Prevent JavaScript access for security + cookie.setMaxAge(60 * 60 * 24); // 1 day expiration time + cookie.setPath("/"); // Make the cookie available for the entire application + if ("https".equalsIgnoreCase(request.getScheme())) { + cookie.setSecure(true); // Secure flag only on HTTPS + } + response.addCookie(cookie); // Add the cookie to the response + } + + public String getJwtTokenFromCookie(HttpServletRequest request) { + return Arrays.stream(request.getCookies()).filter(cookie -> "Jwttoken".equals(cookie.getName())) + .map(Cookie::getValue).findFirst().orElse(null); + } +} diff --git a/src/main/java/com/iemr/common/utils/FilterConfig.java b/src/main/java/com/iemr/common/utils/FilterConfig.java new file mode 100644 index 00000000..1f15a7fa --- /dev/null +++ b/src/main/java/com/iemr/common/utils/FilterConfig.java @@ -0,0 +1,19 @@ +package com.iemr.common.utils; + +import org.springframework.boot.web.servlet.FilterRegistrationBean; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class FilterConfig { + + @Bean + public FilterRegistrationBean jwtUserIdValidationFilter( + JwtAuthenticationUtil jwtAuthenticationUtil) { + FilterRegistrationBean registrationBean = new FilterRegistrationBean<>(); + registrationBean.setFilter(new JwtUserIdValidationFilter(jwtAuthenticationUtil)); + registrationBean.addUrlPatterns("/*"); // Apply filter to all API endpoints + return registrationBean; + } + +} diff --git a/src/main/java/com/iemr/common/utils/JwtAuthenticationUtil.java b/src/main/java/com/iemr/common/utils/JwtAuthenticationUtil.java new file mode 100644 index 00000000..c9e27459 --- /dev/null +++ b/src/main/java/com/iemr/common/utils/JwtAuthenticationUtil.java @@ -0,0 +1,130 @@ +package com.iemr.common.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.stereotype.Component; + +import com.iemr.common.data.users.User; +import com.iemr.common.repository.users.IEMRUserRepositoryCustom; +import com.iemr.common.service.users.IEMRAdminUserServiceImpl; +import com.iemr.common.utils.exception.IEMRException; + +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 IEMRUserRepositoryCustom iEMRUserRepositoryCustom; + private final Logger logger = LoggerFactory.getLogger(this.getClass().getName()); + + @Autowired + private IEMRAdminUserServiceImpl iEMRAdminUserServiceImpl; + + 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 { + // Validate JWT token and extract claims + Claims claims = jwtUtil.validateToken(jwtToken); + + if (claims == null) { + throw new IEMRException("Invalid JWT token."); + } + + String userId = claims.get("userId", String.class); + + // Check if user data is present in Redis + User user = getUserFromCache(userId); + if (user == null) { + // If not in Redis, fetch from DB and cache the result + 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) { + // This method will only be called if the user is not found in Redis. + String redisKey = "user_" + userId; // Redis key format + + // Fetch user from DB + User user = iEMRUserRepositoryCustom.findByUserID(Long.parseLong(userId)); + + if (user != null) { + // Cache the user in Redis for future requests (cache for 30 minutes) + redisTemplate.opsForValue().set(redisKey, user, 30, TimeUnit.MINUTES); + + // Log that the user has been stored in Redis + logger.info("User stored in Redis with key: " + redisKey); + } else { + logger.warn("User not found for userId: " + userId); + } + + return user; + } +} diff --git a/src/main/java/com/iemr/common/utils/JwtUserIdValidationFilter.java b/src/main/java/com/iemr/common/utils/JwtUserIdValidationFilter.java new file mode 100644 index 00000000..0c12c3a6 --- /dev/null +++ b/src/main/java/com/iemr/common/utils/JwtUserIdValidationFilter.java @@ -0,0 +1,111 @@ +package com.iemr.common.utils; + +import java.io.IOException; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Component; + +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; + +@Component +public class JwtUserIdValidationFilter implements Filter { + + private final JwtAuthenticationUtil jwtAuthenticationUtil; + private final Logger logger = LoggerFactory.getLogger(this.getClass().getName()); + + public JwtUserIdValidationFilter(JwtAuthenticationUtil jwtAuthenticationUtil) { + this.jwtAuthenticationUtil = jwtAuthenticationUtil; + } + + @Override + public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) + throws IOException, ServletException { + HttpServletRequest request = (HttpServletRequest) servletRequest; + HttpServletResponse response = (HttpServletResponse) servletResponse; + + String path = request.getRequestURI(); + String contextPath = request.getContextPath(); + logger.info("JwtUserIdValidationFilter invoked for path: " + path); + + // Log cookies for debugging + Cookie[] cookies = request.getCookies(); + if (cookies != null) { + for (Cookie cookie : cookies) { + if ("userId".equals(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 + String jwtTokenFromHeader = request.getHeader("Jwttoken"); + logger.info("JWT token from header: "); + + // Skip login and public endpoints + if (path.equals(contextPath + "/user/userAuthenticate") + || path.equalsIgnoreCase(contextPath + "/user/logOutUserFromConcurrentSession") + || path.startsWith(contextPath + "/public")) { + logger.info("Skipping filter for path: " + path); + filterChain.doFilter(servletRequest, servletResponse); + return; + } + + try { + // Retrieve JWT token from cookies + String jwtTokenFromCookie = getJwtTokenFromCookies(request); + logger.info("JWT token from cookie: "); + + // Determine which token (cookie or header) to validate + String jwtToken = jwtTokenFromCookie != null ? jwtTokenFromCookie : jwtTokenFromHeader; + if (jwtToken == null) { + response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "JWT token not found in cookies or headers"); + return; + } + + // Validate JWT token and userId + boolean isValid = jwtAuthenticationUtil.validateUserIdAndJwtToken(jwtToken); + + if (isValid) { + // If token is valid, allow the request to proceed + filterChain.doFilter(servletRequest, servletResponse); + } else { + response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Invalid JWT token"); + } + } catch (Exception e) { + logger.error("Authorization error: ", e); + response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Authorization error: " + e.getMessage()); + } + } + + private String getJwtTokenFromCookies(HttpServletRequest request) { + Cookie[] cookies = request.getCookies(); + if (cookies != null) { + for (Cookie cookie : cookies) { + if (cookie.getName().equals("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/utils/JwtUtil.java b/src/main/java/com/iemr/common/utils/JwtUtil.java new file mode 100644 index 00000000..a6e8b942 --- /dev/null +++ b/src/main/java/com/iemr/common/utils/JwtUtil.java @@ -0,0 +1,68 @@ +package com.iemr.common.utils; + +import java.security.Key; +import java.util.Date; +import java.util.function.Function; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +import io.jsonwebtoken.Claims; +import io.jsonwebtoken.Jwts; +import io.jsonwebtoken.SignatureAlgorithm; +import io.jsonwebtoken.security.Keys; + +@Component +public class JwtUtil { + + @Value("${jwt.secret}") + private String SECRET_KEY; + + private static final long EXPIRATION_TIME = 24L * 60 * 60 * 1000; // 1 day in milliseconds + + // Generate a key using the secret + private Key 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 JWT Token + public String generateToken(String username, String userId) { + Date now = new Date(); + Date expiryDate = new Date(now.getTime() + EXPIRATION_TIME); + + // Include the userId in the JWT claims + return Jwts.builder().setSubject(username).claim("userId", userId) // Add userId as a claim + .setIssuedAt(now).setExpiration(expiryDate).signWith(getSigningKey(), SignatureAlgorithm.HS256) + .compact(); + } + + // Validate and parse JWT Token + public Claims validateToken(String token) { + try { + // Use the JwtParserBuilder correctly in version 0.12.6 + return Jwts.parser() // Correct method in 0.12.6 to get JwtParserBuilder + .setSigningKey(getSigningKey()) // Set the signing key + .build() // Build the JwtParser + .parseClaimsJws(token) // Parse and validate the token + .getBody(); + } catch (Exception e) { + return null; // Handle token parsing/validation errors + } + } + + public String extractUsername(String token) { + return extractClaim(token, Claims::getSubject); + } + + public T extractClaim(String token, Function claimsResolver) { + final Claims claims = extractAllClaims(token); + return claimsResolver.apply(claims); + } + + private Claims extractAllClaims(String token) { + return Jwts.parser().setSigningKey(getSigningKey()).build().parseClaimsJws(token).getBody(); + } +} \ No newline at end of file