Skip to content

jwt api implementation#48

Merged
ravishanigarapu merged 10 commits into
developfrom
jwt-api
Feb 10, 2025
Merged

jwt api implementation#48
ravishanigarapu merged 10 commits into
developfrom
jwt-api

Conversation

@indraniBan

@indraniBan indraniBan commented Dec 2, 2024

Copy link
Copy Markdown
Contributor

📋 Description

JIRA ID:

Please provide a summary of the change and the motivation behind it. Include relevant context and details.


✅ Type of Change

  • 🐞 Bug fix (non-breaking change which resolves an issue)
  • New feature (non-breaking change which adds functionality)
  • 🔥 Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • 🛠 Refactor (change that is neither a fix nor a new feature)
  • ⚙️ Config change (configuration file or build script updates)
  • 📚 Documentation (updates to docs or readme)
  • 🧪 Tests (adding new or updating existing tests)
  • 🎨 UI/UX (changes that affect the user interface)
  • 🚀 Performance (improves performance)
  • 🧹 Chore (miscellaneous changes that don't modify src or test files)

ℹ️ Additional Information

Please describe how the changes were tested, and include any relevant screenshots, logs, or other information that provides additional context.

Summary by CodeRabbit

  • New Features

    • Added support for JSON Web Tokens (JWT) with new dependencies.
    • Introduced configuration entries for JWT secret management across various environments.
    • Added a new Users class for user management.
    • Implemented cookie management for JWT tokens.
    • Created filters for JWT validation and user ID management.
    • Enhanced Redis configuration for improved caching and user data handling.
  • Bug Fixes

    • Minor formatting adjustments in configuration files.

@coderabbitai

coderabbitai Bot commented Dec 2, 2024

Copy link
Copy Markdown

Walkthrough

This pull request introduces a comprehensive JWT (JSON Web Token) authentication system for the application. The changes include adding JWT-related dependencies to the project, configuring JWT secret keys across different environments, creating utility classes for token generation, validation, and cookie management, and implementing a filter for JWT user ID validation. Additionally, a new entity class for users and configuration for Redis caching are introduced, enhancing the application's security infrastructure and data management capabilities.

Changes

File Change Summary
pom.xml Added three JWT-related dependencies: jjwt-api, jjwt-impl, and jjwt-jackson
src/main/environment/*.properties Added jwt.secret configuration in CI, DEV, TEST, and UAT environment files
src/main/java/com/iemr/ecd/dao/Users.java New entity class for user management with comprehensive user attributes
src/main/java/com/iemr/ecd/utils/mapper/CookieUtil.java New utility class for retrieving cookie values and JWT tokens
src/main/java/com/iemr/ecd/utils/mapper/FilterConfig.java Configuration for registering JWT user ID validation filter
src/main/java/com/iemr/ecd/utils/mapper/JwtAuthenticationUtil.java Utility for JWT token validation and user authentication
src/main/java/com/iemr/ecd/utils/mapper/JwtUserIdValidationFilter.java Servlet filter for JWT token and user ID validation
src/main/java/com/iemr/ecd/utils/mapper/JwtUtil.java Utility for generating, validating, and extracting JWT tokens
src/main/java/com/iemr/ecd/config/RedisConfig.java Updated Redis configuration with caching support
src/main/java/com/iemr/ecd/EcdApiApplication.java Added Redis template configuration method
src/main/java/com/iemr/ecd/repository/ecd/UserLoginRepo.java New interface for user login repository with custom query method

Suggested reviewers

  • ravishanigarapu
  • drtechie

Poem

🐰 JWT Rabbit's Ballad 🔐
In tokens we trust, with secrets so bright,
Hopping through filters, securing each site.
Redis caches our users with magical care,
Cookies and headers, no intruder can dare!
Authentication dances, a security delight! 🚀

Tip

🌐 Web search-backed reviews and chat
  • We have enabled web search-based reviews and chat for all users. This feature allows CodeRabbit to access the latest documentation and information on the web.
  • You can disable this feature by setting web_search: false in the knowledge_base settings.
  • Please share any feedback in the Discord discussion.

Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media?

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR. (Beta)
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 17

🧹 Outside diff range and nitpick comments (14)
src/main/java/com/iemr/ecd/utils/mapper/CookieUtil.java (2)

12-13: Add class-level documentation.

Consider adding Javadoc documentation to describe the class's purpose, responsibilities, and usage in the JWT authentication flow.

 @Service
+/**
+ * Utility class for managing JWT tokens in cookies.
+ * Provides methods for setting, retrieving, and managing secure HTTP-only cookies
+ * used in the JWT authentication flow.
+ */
 public class CookieUtil {

15-25: Consider streamlining implementation and adding documentation.

The implementation is correct but could benefit from the following improvements:

  1. Use Java streams for more concise implementation
  2. Add parameter validation
  3. Add method documentation
+    /**
+     * Retrieves the value of a specific cookie from the HTTP request.
+     * @param request The HTTP request containing the cookies
+     * @param cookieName The name of the cookie to retrieve
+     * @return Optional containing the cookie value if found, empty otherwise
+     * @throws IllegalArgumentException if cookieName is null or empty
+     */
     public Optional<String> getCookieValue(HttpServletRequest request, String cookieName) {
+        if (cookieName == null || cookieName.trim().isEmpty()) {
+            throw new IllegalArgumentException("Cookie name cannot be null or empty");
+        }
+        
         Cookie[] cookies = request.getCookies();
-        if (cookies != null) {
-            for (Cookie cookie : cookies) {
-                if (cookieName.equals(cookie.getName())) {
-                    return Optional.of(cookie.getValue());
-                }
-            }
-        }
-        return Optional.empty();
+        return cookies == null ? Optional.empty() :
+               Arrays.stream(cookies)
+                     .filter(cookie -> cookieName.equals(cookie.getName()))
+                     .map(Cookie::getValue)
+                     .findFirst();
     }
src/main/java/com/iemr/ecd/utils/mapper/JwtAuthenticationUtil.java (4)

28-28: Declare Logger as a static final field

It's a common practice to declare the Logger as a private static final field to ensure there's only one instance per class and to improve performance.

Apply this diff:

-    private final Logger logger = LoggerFactory.getLogger(this.getClass().getName());
+    private static final Logger logger = LoggerFactory.getLogger(JwtAuthenticationUtil.class);

36-60: Decouple utility class from HTTP-specific response handling

The method validateJwtToken returns ResponseEntity<String>, coupling the utility class with HTTP-specific classes. Consider refactoring the method to throw exceptions upon validation failure and return the username directly. This keeps the utility class focused on JWT validation logic and allows controllers to handle HTTP responses appropriately.

Refactor the method as follows:

-    public ResponseEntity<String> validateJwtToken(HttpServletRequest request) {
+    public String validateJwtToken(HttpServletRequest request) throws ECDException {

         Optional<String> jwtTokenOpt = cookieUtil.getCookieValue(request, "Jwttoken");

         if (jwtTokenOpt.isEmpty()) {
-            return ResponseEntity.status(HttpStatus.UNAUTHORIZED)
-                    .body("Error 401: Unauthorized - JWT Token is not set!");
+            throw new ECDException("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!");
+            throw new ECDException("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!");
+            throw new ECDException("Username is missing in JWT Token!");
         }

         // Return the username if valid
-        return ResponseEntity.ok(usernameFromToken);
+        return usernameFromToken;
     }

This change will make the method cleaner and delegates HTTP response handling to the controller layer.


75-77: Handle the possibility of getUserByUserID returning Optional<Users>

To prevent NullPointerException, consider changing the getUserByUserID method to return an Optional<Users> and handle the case where the user is not found.

Apply this diff:

-    Users user = userLoginRepo.getUserByUserID(userIdLong);
-    if (user == null) {
+    Optional<Users> userOpt = userLoginRepo.getUserByUserID(userIdLong);
+    if (userOpt.isEmpty()) {
         throw new ECDException("Invalid User ID.");
     }

+    Users user = userOpt.get();

Ensure that the UserLoginRepo interface is updated accordingly.


87-87: Use SLF4J placeholders instead of string concatenation in logger

Avoid string concatenation in log statements. Using placeholders is more efficient and aligns with best practices.

Apply this diff:

-    logger.error("Validation failed: " + e.getMessage(), e);
+    logger.error("Validation failed: {}", e.getMessage(), e);
src/main/java/com/iemr/ecd/repository/ecd/UserLoginRepo.java (1)

14-15: Simplify repository method by using derived query methods

You can leverage Spring Data JPA's method naming conventions to simplify your code and avoid using explicit @Query annotations. Replace the custom query method with a derived query method.

Apply this change:

-    @Query(" SELECT u FROM Users u WHERE u.userID = :userID AND u.deleted = false ")
-    public Users getUserByUserID(@Param("userID") Long userID);
+    public Users findByUserIDAndDeletedFalse(Long userID);
src/main/environment/ecd_ci.properties (1)

18-18: Ensure JWT secret key is securely managed

Including secret keys directly in properties files can pose a security risk, especially if the file is checked into source control. Consider using environment variables or a secure secrets management system to manage the jwt.secret.

src/main/java/com/iemr/ecd/utils/mapper/FilterConfig.java (1)

8-19: Avoid double registration of filters; use either @Component or FilterRegistrationBean

Since JwtUserIdValidationFilter is annotated with @Component, it will be automatically detected and registered by Spring Boot. Registering it again using FilterRegistrationBean may lead to the filter being registered twice. Consider removing either the @Component annotation from JwtUserIdValidationFilter or the explicit registration in FilterConfig.

To fix this, you can remove the @Component annotation from JwtUserIdValidationFilter and keep the explicit registration:

-@Component
 public class JwtUserIdValidationFilter implements Filter {

Or remove the FilterConfig class if explicit registration is not needed.

src/main/environment/ecd_test.properties (1)

18-18: Ensure JWT secret key is securely managed

Including secret keys directly in properties files can pose a security risk, especially if the file is checked into source control. Consider using environment variables or a secure secrets management system to manage the jwt.secret.

src/main/java/com/iemr/ecd/dao/Users.java (2)

89-97: Enhance audit fields

The audit fields (createdDate, lastModDate) should be automatically managed.

Add JPA auditing:

- @Expose
- @Column(name = "CreatedDate")
- private Timestamp createdDate;
+ @CreatedDate
+ @Column(name = "CreatedDate", updatable = false)
+ private Timestamp createdDate;

- @Expose
- @Column(name = "LastModDate")
- private Timestamp lastModDate;
+ @LastModifiedDate
+ @Column(name = "LastModDate")
+ private Timestamp lastModDate;

1-99: Consider implementing additional security measures

For a JWT-based authentication system, consider these security enhancements:

  1. Add fields for tracking:
    • Last password change
    • Failed login attempts
    • Account lockout status
    • Password reset tokens
  2. Implement password complexity requirements
  3. Add multi-factor authentication support
src/main/java/com/iemr/ecd/utils/mapper/JwtUtil.java (1)

21-21: Consider making token expiration time configurable

The expiration time is hardcoded which reduces flexibility across different environments.

Consider making it configurable via properties:

-private static final long EXPIRATION_TIME = 24 * 60 * 60 * 1000; // 1 day in milliseconds
+@Value("${jwt.expiration.time:86400000}") // Default to 24 hours
+private long expirationTime;
pom.xml (1)

235-254: Update JWT dependencies to latest stable version

The current JWT dependencies are using version 0.11.5, while newer versions are available with security improvements.

Consider updating to the latest stable version:

 <dependency>
     <groupId>io.jsonwebtoken</groupId>
     <artifactId>jjwt-api</artifactId>
-    <version>0.11.5</version>
+    <version>0.12.3</version>
 </dependency>
 <dependency>
     <groupId>io.jsonwebtoken</groupId>
     <artifactId>jjwt-impl</artifactId>
-    <version>0.11.5</version>
+    <version>0.12.3</version>
     <scope>runtime</scope>
 </dependency>
 <dependency>
     <groupId>io.jsonwebtoken</groupId>
     <artifactId>jjwt-jackson</artifactId>
-    <version>0.11.5</version>
+    <version>0.12.3</version>
     <scope>runtime</scope>
 </dependency>
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between d9fe6b2 and fbe6878.

📒 Files selected for processing (12)
  • pom.xml (1 hunks)
  • src/main/environment/ecd_ci.properties (1 hunks)
  • src/main/environment/ecd_dev.properties (1 hunks)
  • src/main/environment/ecd_test.properties (1 hunks)
  • src/main/environment/ecd_uat.properties (1 hunks)
  • src/main/java/com/iemr/ecd/dao/Users.java (1 hunks)
  • src/main/java/com/iemr/ecd/repository/ecd/UserLoginRepo.java (1 hunks)
  • src/main/java/com/iemr/ecd/utils/mapper/CookieUtil.java (1 hunks)
  • src/main/java/com/iemr/ecd/utils/mapper/FilterConfig.java (1 hunks)
  • src/main/java/com/iemr/ecd/utils/mapper/JwtAuthenticationUtil.java (1 hunks)
  • src/main/java/com/iemr/ecd/utils/mapper/JwtUserIdValidationFilter.java (1 hunks)
  • src/main/java/com/iemr/ecd/utils/mapper/JwtUtil.java (1 hunks)
🔇 Additional comments (1)
src/main/environment/ecd_dev.properties (1)

18-18: Same security concerns as UAT properties

The JWT secret configuration has the same security implications as discussed in the UAT properties file.

Comment thread src/main/java/com/iemr/ecd/utils/mapper/CookieUtil.java Outdated
Comment thread src/main/java/com/iemr/ecd/utils/mapper/CookieUtil.java Outdated
Comment thread src/main/java/com/iemr/ecd/utils/mapper/CookieUtil.java Outdated
Comment thread src/main/java/com/iemr/ecd/utils/mapper/JwtAuthenticationUtil.java Outdated
Comment thread src/main/java/com/iemr/ecd/utils/mapper/JwtAuthenticationUtil.java Outdated
Comment thread src/main/java/com/iemr/ecd/utils/mapper/JwtUtil.java Outdated
Comment thread src/main/java/com/iemr/ecd/utils/mapper/JwtUtil.java Outdated
Comment thread src/main/java/com/iemr/ecd/utils/mapper/JwtUtil.java Outdated
Comment thread src/main/java/com/iemr/ecd/utils/mapper/JwtUtil.java Outdated
Comment thread src/main/java/com/iemr/ecd/utils/mapper/JwtUtil.java

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 6

🧹 Outside diff range and nitpick comments (1)
src/main/java/com/iemr/ecd/utils/mapper/CookieUtil.java (1)

15-25: Add parameter validation for cookieName.

While the implementation is good, consider adding parameter validation to prevent NPE:

 public Optional<String> getCookieValue(HttpServletRequest request, String cookieName) {
+    if (request == null || cookieName == null || cookieName.trim().isEmpty()) {
+        return Optional.empty();
+    }
     Cookie[] cookies = request.getCookies();
     if (cookies != null) {
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between fbe6878 and 3afd009.

📒 Files selected for processing (7)
  • pom.xml (6 hunks)
  • src/main/environment/ecd_ci.properties (1 hunks)
  • src/main/java/com/iemr/ecd/utils/mapper/CookieUtil.java (1 hunks)
  • src/main/java/com/iemr/ecd/utils/mapper/FilterConfig.java (1 hunks)
  • src/main/java/com/iemr/ecd/utils/mapper/JwtAuthenticationUtil.java (1 hunks)
  • src/main/java/com/iemr/ecd/utils/mapper/JwtUserIdValidationFilter.java (1 hunks)
  • src/main/java/com/iemr/ecd/utils/mapper/JwtUtil.java (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (5)
  • src/main/environment/ecd_ci.properties
  • src/main/java/com/iemr/ecd/utils/mapper/FilterConfig.java
  • pom.xml
  • src/main/java/com/iemr/ecd/utils/mapper/JwtUserIdValidationFilter.java
  • src/main/java/com/iemr/ecd/utils/mapper/JwtAuthenticationUtil.java
🔇 Additional comments (3)
src/main/java/com/iemr/ecd/utils/mapper/CookieUtil.java (3)

1-13: LGTM: Class structure and imports are well-organized.

The class is properly structured with appropriate imports and Spring service annotation.


27-35: ⚠️ Potential issue

Critical: Address security and maintainability concerns in JWT cookie handling.

The current implementation has several security and maintainability issues:

  1. Missing SameSite attribute (CSRF protection)
  2. Hardcoded values should be externalized
  3. Parameter name doesn't follow Java conventions
  4. Missing parameter validation
+    @Value("${jwt.cookie.name:Jwttoken}")
+    private String jwtCookieName;
+    
+    @Value("${jwt.cookie.maxAge:86400}")
+    private int jwtCookieMaxAge;
+
-    public void addJwtTokenToCookie(String Jwttoken, HttpServletResponse response) {
+    public void addJwtTokenToCookie(String jwtToken, HttpServletResponse response) {
+        if (jwtToken == null || jwtToken.trim().isEmpty()) {
+            throw new IllegalArgumentException("JWT token cannot be null or empty");
+        }
+
-        Cookie cookie = new Cookie("Jwttoken", Jwttoken);
+        Cookie cookie = new Cookie(jwtCookieName, jwtToken);
         cookie.setHttpOnly(true);
         cookie.setSecure(true);
-        cookie.setMaxAge(60 * 60 * 24);
+        cookie.setMaxAge(jwtCookieMaxAge);
         cookie.setPath("/");
+        cookie.setAttribute("SameSite", "Strict");
         response.addCookie(cookie);
     }

37-40: ⚠️ Potential issue

Critical: Fix potential NPE and maintain consistent return types.

The current implementation has several issues:

  1. Missing null check for cookies array (potential NPE)
  2. Inconsistent return type compared to getCookieValue method
  3. Uses hardcoded cookie name
-    public String getJwtTokenFromCookie(HttpServletRequest request) {
-        return Arrays.stream(request.getCookies())
-                .filter(cookie -> "Jwttoken".equals(cookie.getName()))
-                .map(Cookie::getValue)
-                .findFirst()
-                .orElse(null);
+    public Optional<String> getJwtTokenFromCookie(HttpServletRequest request) {
+        if (request == null) {
+            return Optional.empty();
+        }
+        Cookie[] cookies = request.getCookies();
+        return cookies == null ? Optional.empty() :
+               Arrays.stream(cookies)
+                     .filter(cookie -> jwtCookieName.equals(cookie.getName()))
+                     .map(Cookie::getValue)
+                     .findFirst();
     }

Comment thread src/main/java/com/iemr/ecd/utils/mapper/JwtUtil.java
Comment thread src/main/java/com/iemr/ecd/utils/mapper/JwtUtil.java
Comment thread src/main/java/com/iemr/ecd/utils/mapper/JwtUtil.java
Comment thread src/main/java/com/iemr/ecd/utils/mapper/JwtUtil.java
Comment thread src/main/java/com/iemr/ecd/utils/mapper/JwtUtil.java
Comment thread src/main/java/com/iemr/ecd/utils/mapper/CookieUtil.java

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 5

♻️ Duplicate comments (2)
src/main/java/com/iemr/ecd/utils/mapper/JwtAuthenticationUtil.java (2)

22-33: 🛠️ Refactor suggestion

Improve dependency injection and initialization patterns

The current implementation has several structural issues:

  1. Mixing field and constructor injection (matches previous review)
  2. Constructor is missing RedisTemplate initialization
  3. Logger initialization can be improved

Apply this diff:

-    @Autowired
-    private CookieUtil cookieUtil;
-    @Autowired
-    private JwtUtil jwtUtil;
-    @Autowired
-    private RedisTemplate<String, Object> redisTemplate;
-    private final Logger logger = LoggerFactory.getLogger(this.getClass().getName());
+    private final CookieUtil cookieUtil;
+    private final JwtUtil jwtUtil;
+    private final RedisTemplate<String, Object> redisTemplate;
+    private static final Logger logger = LoggerFactory.getLogger(JwtAuthenticationUtil.class);

-    public JwtAuthenticationUtil(CookieUtil cookieUtil, JwtUtil jwtUtil) {
+    @Autowired
+    public JwtAuthenticationUtil(CookieUtil cookieUtil, JwtUtil jwtUtil, 
+            RedisTemplate<String, Object> redisTemplate) {
         this.cookieUtil = cookieUtil;
         this.jwtUtil = jwtUtil;
+        this.redisTemplate = redisTemplate;
     }

62-84: ⚠️ Potential issue

Improve exception handling and add userId validation

The current implementation has several issues that need to be addressed:

  1. Using generic Exception (matches previous review)
  2. Missing userId validation (matches previous review)
  3. Catch block re-throws exception without adding value

Apply this diff:

-    public boolean validateUserIdAndJwtToken(String jwtToken) throws Exception {
+    public boolean validateUserIdAndJwtToken(String jwtToken) throws ECDException {
         try {
             // Validate JWT token and extract claims
             Claims claims = jwtUtil.validateToken(jwtToken);

             if (claims == null) {
                 throw new ECDException("Invalid JWT token.");
             }

             String userId = claims.get("userId", String.class);
+            if (userId == null || userId.isEmpty()) {
+                throw new ECDException("User ID is missing in JWT claims");
+            }
+            
+            try {
+                Long.parseLong(userId);
+            } catch (NumberFormatException e) {
+                throw new ECDException("Invalid user ID format in JWT claims");
+            }

             // Check if user data is present in Redis
             Users user = getUserFromCache(userId);
             if (user == null) {
-                throw new Exception("Invalid User ID.");
+                throw new ECDException("User not found in cache");
             }

             return true; // Valid userId and JWT token
-        } catch (Exception e) {
+        } catch (ECDException e) {
+            logger.error("JWT validation failed: {}", e.getMessage());
+            throw e;
+        } catch (Exception e) {
             logger.error("Validation failed: " + e.getMessage(), e);
-            throw new Exception("Validation error: " + e.getMessage(), e);
+            throw new ECDException("Unexpected error during validation", e);
         }
     }
🧹 Nitpick comments (2)
src/main/java/com/iemr/ecd/utils/mapper/JwtAuthenticationUtil.java (2)

35-60: Enhance error handling and maintainability

Consider the following improvements:

  1. Extract cookie name and error messages as constants
  2. Add logging for validation failures
  3. Consider using a custom exception handler instead of direct ResponseEntity creation

Apply this diff:

+    private static final String JWT_COOKIE_NAME = "Jwttoken";
+    private static final String ERROR_TOKEN_NOT_SET = "Error 401: Unauthorized - JWT Token is not set!";
+    private static final String ERROR_INVALID_TOKEN = "Error 401: Unauthorized - Invalid JWT Token!";
+    private static final String ERROR_MISSING_USERNAME = "Error 401: Unauthorized - Username is missing!";

     public ResponseEntity<String> validateJwtToken(HttpServletRequest request) {
-        Optional<String> jwtTokenOpt = cookieUtil.getCookieValue(request, "Jwttoken");
+        Optional<String> jwtTokenOpt = cookieUtil.getCookieValue(request, JWT_COOKIE_NAME);

         if (jwtTokenOpt.isEmpty()) {
+            logger.warn("JWT token not found in cookies");
             return ResponseEntity.status(HttpStatus.UNAUTHORIZED)
-                    .body("Error 401: Unauthorized - JWT Token is not set!");
+                    .body(ERROR_TOKEN_NOT_SET);
         }

         String jwtToken = jwtTokenOpt.get();

         // Validate the token
         Claims claims = jwtUtil.validateToken(jwtToken);
         if (claims == null) {
+            logger.warn("Invalid JWT token detected");
             return ResponseEntity.status(HttpStatus.UNAUTHORIZED)
-                .body("Error 401: Unauthorized - Invalid JWT Token!");
+                .body(ERROR_INVALID_TOKEN);
         }

         // Extract username from token
         String usernameFromToken = claims.getSubject();
         if (usernameFromToken == null || usernameFromToken.isEmpty()) {
+            logger.warn("Username missing in JWT token");
             return ResponseEntity.status(HttpStatus.UNAUTHORIZED)
-                    .body("Error 401: Unauthorized - Username is missing!");
+                    .body(ERROR_MISSING_USERNAME);
         }

+        logger.debug("JWT token validated successfully for user: {}", usernameFromToken);
         // Return the username if valid
         return ResponseEntity.ok(usernameFromToken);
     }

86-97: Enhance Redis key management and logging

Consider the following improvements:

  1. Extract Redis key prefix as a constant
  2. Include userId in log messages
  3. Add method documentation

Apply this diff:

+    private static final String REDIS_USER_KEY_PREFIX = "user_";

+    /**
+     * Retrieves a user from Redis cache using their userId.
+     *
+     * @param userId the unique identifier of the user
+     * @return the Users object if found, null otherwise
+     */
     private Users getUserFromCache(String userId) {
-        String redisKey = "user_" + userId; // The Redis key format
+        String redisKey = REDIS_USER_KEY_PREFIX + userId;
         Users user = (Users) redisTemplate.opsForValue().get(redisKey);

         if (user == null) {
-            logger.warn("User not found in Redis.");
+            logger.warn("User not found in Redis for userId: {}", userId);
         } else {
-            logger.info("User fetched successfully from Redis.");
+            logger.debug("User fetched successfully from Redis for userId: {}", userId);
         }

         return user; // Returns null if not found
     }
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between fd0eb9a and 0ca92ba.

📒 Files selected for processing (4)
  • src/main/java/com/iemr/ecd/EcdApiApplication.java (2 hunks)
  • src/main/java/com/iemr/ecd/config/RedisConfig.java (2 hunks)
  • src/main/java/com/iemr/ecd/dao/Users.java (1 hunks)
  • src/main/java/com/iemr/ecd/utils/mapper/JwtAuthenticationUtil.java (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/main/java/com/iemr/ecd/dao/Users.java
🔇 Additional comments (1)
src/main/java/com/iemr/ecd/EcdApiApplication.java (1)

Line range hint 41-41: Security concern: SecurityAutoConfiguration is excluded

The application excludes Spring Security auto-configuration (SecurityAutoConfiguration.class) which is concerning for a JWT implementation. This might leave the application vulnerable.

Consider:

  1. Removing the exclusion
  2. Implementing proper security configuration
  3. Adding JWT security filters

Comment thread src/main/java/com/iemr/ecd/config/RedisConfig.java
Comment thread src/main/java/com/iemr/ecd/config/RedisConfig.java
Comment thread src/main/java/com/iemr/ecd/config/RedisConfig.java
Comment thread src/main/java/com/iemr/ecd/EcdApiApplication.java
Comment thread src/main/java/com/iemr/ecd/utils/mapper/JwtAuthenticationUtil.java
@sonarqubecloud

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (2)
src/main/java/com/iemr/ecd/utils/mapper/JwtAuthenticationUtil.java (2)

39-64: Consider decomposing the method for better maintainability.

The method handles multiple responsibilities: cookie retrieval, token validation, and response generation. Consider extracting these into separate private methods following the Single Responsibility Principle.

Apply this diff:

-    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 ResponseEntity<String> validateJwtToken(HttpServletRequest request) {
+        Optional<String> jwtTokenOpt = getJwtTokenFromCookie(request);
+        if (jwtTokenOpt.isEmpty()) {
+            return createUnauthorizedResponse("JWT Token is not set!");
+        }
+
+        Claims claims = validateJwtTokenAndGetClaims(jwtTokenOpt.get());
+        if (claims == null) {
+            return createUnauthorizedResponse("Invalid JWT Token!");
+        }
+
+        String username = extractAndValidateUsername(claims);
+        if (username == null) {
+            return createUnauthorizedResponse("Username is missing!");
+        }
+
+        return ResponseEntity.ok(username);
+    }
+
+    private Optional<String> getJwtTokenFromCookie(HttpServletRequest request) {
+        return cookieUtil.getCookieValue(request, "Jwttoken");
+    }
+
+    private Claims validateJwtTokenAndGetClaims(String jwtToken) {
+        return jwtUtil.validateToken(jwtToken);
+    }
+
+    private String extractAndValidateUsername(Claims claims) {
+        String username = claims.getSubject();
+        return (username != null && !username.isEmpty()) ? username : null;
+    }
+
+    private ResponseEntity<String> createUnauthorizedResponse(String message) {
+        return ResponseEntity.status(HttpStatus.UNAUTHORIZED)
+                .body("Error 401: Unauthorized - " + message);
+    }

94-105: Enhance Redis key management and type safety.

Consider the following improvements:

  1. Use a constant for the Redis key prefix.
  2. Add type safety checks before casting.

Apply this diff:

+    private static final String REDIS_USER_KEY_PREFIX = "user_";
+
     private Users getUserFromCache(String userId) {
-        String redisKey = "user_" + userId; // The Redis key format
-        Users user = (Users) redisTemplate.opsForValue().get(redisKey);
+        String redisKey = REDIS_USER_KEY_PREFIX + userId;
+        Object value = redisTemplate.opsForValue().get(redisKey);
+        Users user = null;
+        if (value instanceof Users) {
+            user = (Users) value;
+        }

         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;
     }
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0ca92ba and 7f5fa76.

📒 Files selected for processing (7)
  • pom.xml (6 hunks)
  • src/main/environment/ecd_ci.properties (1 hunks)
  • src/main/environment/ecd_dev.properties (1 hunks)
  • src/main/environment/ecd_test.properties (1 hunks)
  • src/main/environment/ecd_uat.properties (1 hunks)
  • src/main/java/com/iemr/ecd/repository/ecd/UserLoginRepo.java (1 hunks)
  • src/main/java/com/iemr/ecd/utils/mapper/JwtAuthenticationUtil.java (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (5)
  • src/main/environment/ecd_ci.properties
  • src/main/environment/ecd_test.properties
  • src/main/environment/ecd_uat.properties
  • src/main/environment/ecd_dev.properties
  • pom.xml
🧰 Additional context used
📓 Learnings (1)
src/main/java/com/iemr/ecd/utils/mapper/JwtAuthenticationUtil.java (1)
Learnt from: sandipkarmakar3
PR: PSMRI/ECD-API#48
File: src/main/java/com/iemr/ecd/utils/mapper/JwtUtil.java:32-40
Timestamp: 2025-02-03T12:54:54.932Z
Learning: Token replay protection using nonce claims and token blacklisting was deemed unnecessary for the JWT implementation in the ECD-API project.
⏰ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: Analyze (java)
🔇 Additional comments (3)
src/main/java/com/iemr/ecd/repository/ecd/UserLoginRepo.java (1)

13-14: Verify input sanitization and consider adding an index.

The query looks good, but consider the following:

  1. Ensure that the userID parameter is properly sanitized before being used in the query to prevent SQL injection.
  2. Consider adding an index on the userID and deleted columns to optimize query performance.

Run the following script to check for existing indexes:

src/main/java/com/iemr/ecd/utils/mapper/JwtAuthenticationUtil.java (2)

24-37: Avoid mixing field injection and constructor injection.

The class is mixing field injection and constructor injection, which can lead to inconsistencies. Additionally, the constructor doesn't initialize all dependencies.


66-92: Validate 'userId' claim before using it.

Ensure that the 'userId' claim is present and valid before using it.

Avoid throwing generic Exception.

Throwing a generic Exception makes error handling difficult for callers.

Comment thread src/main/java/com/iemr/ecd/utils/mapper/JwtAuthenticationUtil.java
@sonarqubecloud

sonarqubecloud Bot commented Feb 6, 2025

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (1)
.github/workflows/package.yml (1)

38-38: Remove Trailing Whitespace
Static analysis flagged trailing spaces on line 38. Removing these extraneous spaces will ensure that the YAML file adheres to linting standards and improves overall consistency.

🧰 Tools
🪛 YAMLlint (1.35.1)

[error] 38-38: trailing spaces

(trailing-spaces)

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 597cc12 and cbd4e72.

📒 Files selected for processing (1)
  • .github/workflows/package.yml (2 hunks)
🧰 Additional context used
🪛 YAMLlint (1.35.1)
.github/workflows/package.yml

[error] 38-38: trailing spaces

(trailing-spaces)

⏰ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: Analyze (java)
🔇 Additional comments (1)
.github/workflows/package.yml (1)

29-29: JDK Distribution Update – Confirm 'adopt' Usage
The distribution parameter for the Setup JDK 17 step has been updated from 'zulu' to 'adopt'. Please verify that this change aligns with your build and runtime requirements, as different JDK distributions can have subtle differences in performance or behavior.

@ravishanigarapu ravishanigarapu left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

looks ok

@ravishanigarapu
ravishanigarapu merged commit 3d6cfbc into develop Feb 10, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants