Skip to content

Conversation

@drtechie
Copy link
Member

@drtechie drtechie commented May 30, 2025

πŸ“‹ Description

JIRA ID: AMM-1507

Check if jti is present in deny list before authenticating.

βœ… 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)

Summary by CodeRabbit

  • New Features

    • Added support for token revocation, allowing tokens to be invalidated and denied access if necessary.
    • Introduced token denylisting, enabling detection of revoked or invalidated tokens.
  • Bug Fixes

    • Improved handling of invalid or expired tokens, ensuring null is returned on validation failure.
  • Refactor

    • Updated JWT validation to use modern parsing methods.
    • Enhanced claim extraction for better null safety.
  • Chores

    • Removed token generation functionality.

@coderabbitai
Copy link

coderabbitai bot commented May 30, 2025

Walkthrough

A new TokenDenylist component was introduced to manage JWT revocation using Redis. The JwtUtil class was refactored to use updated JWT parsing APIs, removed token generation, and now supports token invalidation and denylisting by JWT ID. Claim extraction methods were updated for null safety and modernized parsing.

Changes

File(s) Change Summary
.../utils/JwtUtil.java Refactored for new JWT parsing/signing APIs, removed token generation, added token invalidation and denylisting using TokenDenylist, improved null safety in claim extraction.
.../utils/TokenDenylist.java New class managing JWT denylist with Redis, providing methods to add tokens to the denylist and check denylist status.

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant JwtUtil
    participant TokenDenylist
    participant Redis

    Client->>JwtUtil: validateToken(token)
    JwtUtil->>JwtUtil: parse token, extract jti
    JwtUtil->>TokenDenylist: isTokenDenylisted(jti)
    TokenDenylist->>Redis: get(denied_jti)
    Redis-->>TokenDenylist: return value
    TokenDenylist-->>JwtUtil: true/false
    alt Token is denylisted
        JwtUtil-->>Client: return null
    else Token is valid
        JwtUtil-->>Client: return Claims
    end
Loading
sequenceDiagram
    participant Client
    participant JwtUtil
    participant TokenDenylist
    participant Redis

    Client->>JwtUtil: invalidateToken(token)
    JwtUtil->>JwtUtil: parse token, extract jti, expiration
    JwtUtil->>TokenDenylist: addTokenToDenylist(jti, expirationTime)
    TokenDenylist->>Redis: set(denied_jti, true, expirationTime)
    Redis-->>TokenDenylist: OK
    TokenDenylist-->>JwtUtil: (void)
    JwtUtil-->>Client: (void)
Loading
✨ Finishing Touches
  • πŸ“ Generate Docstrings

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

❀️ Share
πŸͺ§ 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.
    • Explain this complex logic.
    • 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 explain this code block.
    • @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 explain its main purpose.
    • @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.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

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.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @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.

@sonarqubecloud
Copy link

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

πŸ“œ Review details

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

πŸ“₯ Commits

Reviewing files that changed from the base of the PR and between bf63df8 and 7e39ef8.

πŸ“’ Files selected for processing (2)
  • src/main/java/com/iemr/tm/utils/JwtUtil.java (2 hunks)
  • src/main/java/com/iemr/tm/utils/TokenDenylist.java (1 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (1)
src/main/java/com/iemr/tm/utils/TokenDenylist.java (1)
src/main/java/com/iemr/tm/utils/JwtUtil.java (1)
  • Component (15-87)
⏰ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: Analyze (java-kotlin)
πŸ”‡ Additional comments (2)
src/main/java/com/iemr/tm/utils/TokenDenylist.java (1)

27-38: Good resilience pattern for Redis failures

The method gracefully handles Redis failures by returning false, which prevents service disruption. This fail-open approach prioritizes availability over strict token denial enforcement.

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

35-48: LGTM! Proper integration with denylist

The token validation correctly checks the denylist after successful parsing, with appropriate null checks for the JTI.

Comment on lines +15 to +25
public void addTokenToDenylist(String jti, Long expirationTime) {
if (jti != null && expirationTime != null && expirationTime > 0) {
try {
String key = PREFIX + jti;
redisTemplate.opsForValue().set(key, true);
redisTemplate.expire(key, expirationTime, TimeUnit.MILLISECONDS);
} catch (Exception e) {
throw new RuntimeException("Failed to add token to denylist", e);
}
}
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Use atomic operation for setting key with expiration

The current implementation sets the value and expiration in two separate operations. If the expire call fails, the key will remain in Redis indefinitely without expiration.

Apply this diff to use an atomic operation:

 public void addTokenToDenylist(String jti, Long expirationTime) {
     if (jti != null && expirationTime != null && expirationTime > 0) {
         try {
             String key = PREFIX + jti;
-            redisTemplate.opsForValue().set(key, true);
-            redisTemplate.expire(key, expirationTime, TimeUnit.MILLISECONDS);
+            redisTemplate.opsForValue().set(key, true, expirationTime, TimeUnit.MILLISECONDS);
         } catch (Exception e) {
             throw new RuntimeException("Failed to add token to denylist", e);
         }
     }
 }
πŸ“ 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
public void addTokenToDenylist(String jti, Long expirationTime) {
if (jti != null && expirationTime != null && expirationTime > 0) {
try {
String key = PREFIX + jti;
redisTemplate.opsForValue().set(key, true);
redisTemplate.expire(key, expirationTime, TimeUnit.MILLISECONDS);
} catch (Exception e) {
throw new RuntimeException("Failed to add token to denylist", e);
}
}
}
public void addTokenToDenylist(String jti, Long expirationTime) {
if (jti != null && expirationTime != null && expirationTime > 0) {
try {
String key = PREFIX + jti;
redisTemplate.opsForValue().set(key, true, expirationTime, TimeUnit.MILLISECONDS);
} catch (Exception e) {
throw new RuntimeException("Failed to add token to denylist", e);
}
}
}
πŸ€– Prompt for AI Agents
In src/main/java/com/iemr/tm/utils/TokenDenylist.java between lines 15 and 25,
the method addTokenToDenylist sets the Redis key and expiration in two separate
calls, risking keys without expiration if expire fails. Fix this by using a
single atomic Redis operation that sets the key with its expiration time in one
call, such as redisTemplate.opsForValue().set(key, true, expirationTime,
TimeUnit.MILLISECONDS), to ensure the key always has the correct TTL.

Comment on lines +65 to +68
} catch (Exception e) {
// Log error but don't throw - token might be invalid already
throw new RuntimeException("Failed to invalidate token", e);
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Fix inconsistency between comment and implementation

The comment states "Log error but don't throw" but the code throws a RuntimeException.

Either update the comment or change the implementation:

         } catch (Exception e) {
-            // Log error but don't throw - token might be invalid already
+            // Propagate error - token invalidation should be reliable
             throw new RuntimeException("Failed to invalidate token", e);
         }

Or if you want to follow the comment's intent:

         } catch (Exception e) {
-            // Log error but don't throw - token might be invalid already
-            throw new RuntimeException("Failed to invalidate token", e);
+            // Log error but don't throw - token might be invalid already
+            // logger.error("Failed to invalidate token", e);
         }
πŸ“ 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
} catch (Exception e) {
// Log error but don't throw - token might be invalid already
throw new RuntimeException("Failed to invalidate token", e);
}
} catch (Exception e) {
// Propagate error - token invalidation should be reliable
throw new RuntimeException("Failed to invalidate token", e);
}
πŸ€– Prompt for AI Agents
In src/main/java/com/iemr/tm/utils/JwtUtil.java around lines 65 to 68, the
comment says to log the error without throwing, but the code throws a
RuntimeException. To fix this inconsistency, either update the comment to
reflect that an exception is thrown or modify the catch block to log the error
without throwing any exception, following the comment's intent.

Comment on lines 80 to 86
private Claims extractAllClaims(String token) {
return Jwts.parser().setSigningKey(getSigningKey()).build().parseClaimsJws(token).getBody();
return Jwts.parser()
.verifyWith(getSigningKey())
.build()
.parseSignedClaims(token)
.getPayload();
}
Copy link

Choose a reason for hiding this comment

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

πŸ› οΈ Refactor suggestion

Consider adding exception handling for invalid tokens

The extractAllClaims method will throw exceptions for invalid tokens. While validateToken handles this properly, invalidateToken expects this method to return null for invalid tokens based on the null check at line 58.

Consider wrapping in try-catch to align with the usage pattern:

 private Claims extractAllClaims(String token) {
+    try {
         return Jwts.parser()
             .verifyWith(getSigningKey())
             .build()
             .parseSignedClaims(token)
             .getPayload();
+    } catch (Exception e) {
+        return null;
+    }
 }
πŸ“ 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
private Claims extractAllClaims(String token) {
return Jwts.parser().setSigningKey(getSigningKey()).build().parseClaimsJws(token).getBody();
return Jwts.parser()
.verifyWith(getSigningKey())
.build()
.parseSignedClaims(token)
.getPayload();
}
private Claims extractAllClaims(String token) {
try {
return Jwts.parser()
.verifyWith(getSigningKey())
.build()
.parseSignedClaims(token)
.getPayload();
} catch (Exception e) {
return null;
}
}
πŸ€– Prompt for AI Agents
In src/main/java/com/iemr/tm/utils/JwtUtil.java around lines 80 to 86, the
extractAllClaims method currently throws exceptions for invalid tokens, but
invalidateToken expects it to return null on invalid tokens. To fix this, wrap
the method's logic in a try-catch block that catches relevant exceptions (e.g.,
JwtException) and returns null when an exception occurs, ensuring consistency
with the null check usage.

Copy link
Contributor

@srishtigrp78 srishtigrp78 left a comment

Choose a reason for hiding this comment

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

approved

@srishtigrp78 srishtigrp78 merged commit 64a4f9d into develop Jun 2, 2025
8 checks passed
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