Skip to content
Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 34 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,40 @@
<artifactId>spring-web</artifactId>
<version>6.1.12</version>
</dependency>

<!-- Jersey JSON Processing -->
<dependency>
<groupId>org.glassfish.jersey.media</groupId>
<artifactId>jersey-media-json-processing</artifactId>
<version>2.30.1</version>
</dependency>
Comment on lines +222 to +227
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

πŸ› οΈ Refactor suggestion

Update Jersey JSON Processing version for compatibility.

The version of jersey-media-json-processing (2.30.1) is outdated and may have compatibility issues with Spring Boot 3.x and Jakarta EE.

Consider updating to a version that supports Jakarta EE:

<dependency>
    <groupId>org.glassfish.jersey.media</groupId>
    <artifactId>jersey-media-json-processing</artifactId>
-    <version>2.30.1</version>
+    <version>3.1.3</version>
</dependency>
πŸ“ Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<!-- Jersey JSON Processing -->
<dependency>
<groupId>org.glassfish.jersey.media</groupId>
<artifactId>jersey-media-json-processing</artifactId>
<version>2.30.1</version>
</dependency>
<!-- Jersey JSON Processing -->
<dependency>
<groupId>org.glassfish.jersey.media</groupId>
<artifactId>jersey-media-json-processing</artifactId>
<version>3.1.3</version>
</dependency>


<!-- JAXB Runtime Implementation -->
<dependency>
<groupId>org.glassfish.jaxb</groupId>
<artifactId>jaxb-runtime</artifactId>
<version>2.3.1</version>
</dependency>
Comment on lines +230 to +234
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

πŸ› οΈ Refactor suggestion

Update JAXB Runtime version for compatibility.

The project is using JAXB Runtime 2.3.1, which is quite old and may not be fully compatible with Jakarta EE in Spring Boot 3.x. Spring Boot 3 uses Jakarta EE 9+ which requires newer JAXB versions.

Consider updating to a newer version that aligns with Jakarta EE:

<dependency>
    <groupId>org.glassfish.jaxb</groupId>
    <artifactId>jaxb-runtime</artifactId>
-    <version>2.3.1</version>
+    <version>4.0.0</version>
</dependency>
πŸ“ Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<dependency>
<groupId>org.glassfish.jaxb</groupId>
<artifactId>jaxb-runtime</artifactId>
<version>2.3.1</version>
</dependency>
<dependency>
<groupId>org.glassfish.jaxb</groupId>
<artifactId>jaxb-runtime</artifactId>
<version>4.0.0</version>
</dependency>

<!-- https://mvnrepository.com/artifact/io.jsonwebtoken/jjwt-api -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
<version>0.12.6</version>
</dependency>
<!-- https://mvnrepository.com/artifact/io.jsonwebtoken/jjwt-impl -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-impl</artifactId>
<version>0.12.6</version>
<scope>runtime</scope>
</dependency>
<!-- https://mvnrepository.com/artifact/io.jsonwebtoken/jjwt-jackson -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-jackson</artifactId>
<version>0.12.6</version>
<scope>runtime</scope>
</dependency>
<!--END NEW DEPENDENCIES ADDED-->
</dependencies>
<profiles>
Expand Down
142 changes: 142 additions & 0 deletions src/main/java/com/iemr/common/identity/domain/iemr/M_User.java
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

@kevalkanp1011 are all these attributes required for JWT checks?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

not all but username, password, userid are required. i followed similar implementation available in other repositories

Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
package com.iemr.common.identity.domain.iemr;

import com.google.gson.annotations.Expose;
import jakarta.persistence.*;
import lombok.Data;
import java.sql.Date;
import java.sql.Timestamp;
import java.time.LocalDate;

@Entity
@Table(name = "m_User",schema = "db_iemr")
@Data
public class M_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;
Comment on lines +64 to +66
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot Apr 2, 2025

Choose a reason for hiding this comment

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

⚠️ Potential issue

Secure sensitive information storage.

The password field is currently exposed via the @Expose annotation, which means it could be included in serialized output. Additionally, there's no indication the password is being stored securely (hashed and salted).

Consider:

  1. Removing @Expose annotation from the password field
  2. Add a comment or annotation indicating password handling mechanism
  3. Ensure the actual storage implementation follows security best practices for passwords
-    @Expose
    @Column(name="Password")
    private String password;
πŸ“ Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
@Expose
@Column(name="Password")
private String password;
@Column(name="Password")
private String password;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Seems like the humans are having a chat. I'll hop back into my burrow for now. If you need me again, just tag @coderabbitai in a new comment, and I'll come hopping out!

@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 M_User() {
// TODO Auto-generated constructor stub
}

public M_User(Integer userID, String userName) {
// TODO Auto-generated constructor stub
this.userID=userID;
this.userName=userName;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package com.iemr.common.identity.repo.iemr;

import com.iemr.common.identity.domain.iemr.M_User;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

@Repository
public interface EmployeeMasterRepo extends JpaRepository<M_User,Integer> {
M_User findByUserID(Integer userID);

M_User getUserByUserID(Integer parseLong);
}
41 changes: 41 additions & 0 deletions src/main/java/com/iemr/common/identity/utils/CookieUtil.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package com.iemr.common.identity.utils;

import jakarta.servlet.http.Cookie;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.stereotype.Service;
import java.util.Arrays;
import java.util.Optional;

@Service
public class CookieUtil {

public Optional<String> 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
}
Comment on lines +25 to +35
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

πŸ› οΈ Refactor suggestion

Add CSRF mitigation measures to cookies.
While you already set HttpOnly and conditionally set Secure, consider adding the SameSite attribute to prevent CSRF attacks. For modern Java Servlet APIs, you may need custom headers or frameworks that support setting SameSite=Lax or Strict.

🧰 Tools
πŸͺ› ast-grep (0.31.1)

[warning] 33-33: A cookie was detected without setting the 'secure' flag. The 'secure' flag for cookies prevents the client from transmitting the cookie over insecure channels such as HTTP. Set the 'secure' flag by calling '.setSecure(true);'.
Context: response.addCookie(cookie);
Note: [CWE-614] Sensitive Cookie in HTTPS Session Without 'Secure' Attribute. [REFERENCES]
- https://owasp.org/www-community/controls/SecureCookieAttribute

(cookie-missing-secure-flag-java)


[warning] 33-33: The application does not appear to verify inbound requests which can lead to a Cross-site request forgery (CSRF) vulnerability. If the application uses cookie-based authentication, an attacker can trick users into sending authenticated HTTP requests without their knowledge from any arbitrary domain they visit. To prevent this vulnerability start by identifying if the framework or library leveraged has built-in features or offers plugins for CSRF protection. CSRF tokens should be unique and securely random. The Synchronizer Token or Double Submit Cookie patterns with defense-in-depth mechanisms such as the sameSite cookie flag can help prevent CSRF. For more information, see: [Cross-site request forgery prevention](https://cheatsheetseries.owasp.org/cheatsheets/Cross-Site_Req\ uest_Forgery_Prevention_Cheat_Sheet.html).
Context: response.addCookie(cookie);
Note: [CWE-352] Cross-Site Request Forgery (CSRF). [REFERENCES]
- https://stackoverflow.com/questions/42717210/samesite-cookie-in-java-application

(cookie-missing-samesite-java)


public String getJwtTokenFromCookie(HttpServletRequest request) {
return Arrays.stream(request.getCookies()).filter(cookie -> "Jwttoken".equals(cookie.getName()))
.map(Cookie::getValue).findFirst().orElse(null);
}
}
19 changes: 19 additions & 0 deletions src/main/java/com/iemr/common/identity/utils/FilterConfig.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package com.iemr.common.identity.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> jwtUserIdValidationFilter(
JwtAuthenticationUtil jwtAuthenticationUtil) {
FilterRegistrationBean<JwtUserIdValidationFilter> registrationBean = new FilterRegistrationBean<>();
registrationBean.setFilter(new JwtUserIdValidationFilter(jwtAuthenticationUtil));
registrationBean.addUrlPatterns("/*"); // Apply filter to all API endpoints
return registrationBean;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
package com.iemr.common.identity.utils;

import com.iemr.common.identity.domain.iemr.M_User;
import com.iemr.common.identity.repo.iemr.EmployeeMasterRepo;
import com.iemr.common.identity.utils.exception.IEMRException;
import io.jsonwebtoken.Claims;
import jakarta.servlet.http.HttpServletRequest;
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 java.util.Optional;
import java.util.concurrent.TimeUnit;

@Component
public class JwtAuthenticationUtil {

@Autowired
private CookieUtil cookieUtil;
@Autowired
private JwtUtil jwtUtil;
@Autowired
private RedisTemplate<String, Object> redisTemplate;
@Autowired
private EmployeeMasterRepo userLoginRepo;
private final Logger logger = LoggerFactory.getLogger(this.getClass().getName());

public JwtAuthenticationUtil(CookieUtil cookieUtil, JwtUtil jwtUtil) {
this.cookieUtil = cookieUtil;
this.jwtUtil = jwtUtil;
}

public ResponseEntity<String> validateJwtToken(HttpServletRequest request) {
Optional<String> 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
M_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 M_User getUserFromCache(String userId) {
String redisKey = "user_" + userId; // The Redis key format
M_User user = (M_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 M_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
M_User user = userLoginRepo.getUserByUserID(Integer.parseInt(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;
}
}
Loading
Loading