Skip to content

feat: implement Secure Chat with Healthcare Providers (#218) - #290

Open
Dev1822 wants to merge 3 commits into
vallabhatech:mainfrom
Dev1822:feature/secure-chat
Open

feat: implement Secure Chat with Healthcare Providers (#218)#290
Dev1822 wants to merge 3 commits into
vallabhatech:mainfrom
Dev1822:feature/secure-chat

Conversation

@Dev1822

@Dev1822 Dev1822 commented Jul 20, 2026

Copy link
Copy Markdown
Contributor

Description

Implemented a secure, real-time chat interface for users to communicate with healthcare providers. This feature leverages WebSockets via socket.io for real-time bidirectional messaging and implements AES-256-CBC symmetric encryption at the database level to ensure HIPAA compliance and protect sensitive health information.

Additionally, fixed pre-existing backend syntax errors in auth.js and resolved missing dependency issues (express-rate-limit, libsodium-wrappers) to ensure the server starts properly.

Fixes #218

Type of change

  • Bug fix (non-breaking change which fixes 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)
  • This change requires a documentation update

How Has This Been Tested?

Please describe the tests that you ran to verify your changes.

  • Backend Syntax & Start-up: Verified that the Express server spins up without crashing and that all routes, including the new socket.io server, mount successfully.
  • API & Encryption Verification: Confirmed that the Message Mongoose schema correctly intercepts and encrypts data before saving to the database using AES encryption, and decrypts it dynamically upon retrieval.

Checklist:

  • My code follows the style guidelines of this project
  • I have performed a self-review of my own code
  • I have commented my code, particularly in hard-to-understand areas
  • I have made corresponding changes to the documentation
  • My changes generate no new warnings
  • I have added tests that prove my fix is effective or that my feature works

Summary by CodeRabbit

  • New Features
    • Added a Secure Chat page with a new navigation entry and route.
    • Introduced real-time conversations with message history, timestamps, and typing indicators.
    • Added provider discovery to start new chats from available contacts.
  • Security
    • Implemented encrypted chat message storage.
    • Secured chat APIs and real-time messaging with JWT-based access controls and rate limiting.
  • Bug Fixes
    • Standardized email format validation for registration and login using shared logic.

@vercel

vercel Bot commented Jul 20, 2026

Copy link
Copy Markdown

@Dev1822 is attempting to deploy a commit to the vallabhatech's projects Team on Vercel.

A member of the Team first needs to authorize it.

@coderabbitai

coderabbitai Bot commented Jul 20, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds a secure chat feature with encrypted message persistence, authenticated REST and Socket.IO communication, provider/conversation management, and a routed React/MUI chat interface.

Changes

Secure chat

Layer / File(s) Summary
Chat data and encryption
server/models/Conversation.js, server/models/Message.js, server/utils/encryption.js
Adds conversation and message schemas, AES-256-GCM encryption, legacy payload decryption, and Mongoose content accessors.
Authenticated chat backend
server/routes/chat.js, server/index.js, server/routes/auth.js, server/package.json
Adds protected conversation, message, and provider endpoints; mounts the router; centralizes email validation; and wires authenticated Socket.IO room events.
Chat client and navigation
src/pages/SecureChat.jsx, src/services/chatService.js, src/App.jsx, src/i18n/locales/en.json, package.json
Adds authenticated API calls, realtime chat state and UI, navigation and routing, and the Socket.IO client dependency.

Estimated code review effort: 4 (Complex) | ~60 minutes

Possibly related PRs

Suggested labels: enhancement, bug, backend, frontend, ui/ux

Suggested reviewers: vallabhatech

Sequence Diagram(s)

sequenceDiagram
  participant User
  participant SecureChat
  participant ChatAPI
  participant SocketIO
  participant Database
  User->>SecureChat: Open chat page
  SecureChat->>ChatAPI: Fetch conversations and providers
  ChatAPI->>Database: Query chat data
  SecureChat->>SocketIO: Authenticate with JWT
  SecureChat->>SocketIO: Join conversation room
  SecureChat->>ChatAPI: Persist message
  ChatAPI->>Database: Save encrypted message
  SecureChat->>SocketIO: Broadcast sendMessage
  SocketIO-->>SecureChat: Deliver newMessage
Loading
🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Linked Issues check ⚠️ Warning The chat flow is implemented, but message encryption is backend-decryptable AES at rest, not end-to-end encryption as required by #218. Implement true E2EE so the server cannot decrypt message content, and store only ciphertext with client-side key management.
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly names the secure chat feature and matches the main change set.
Out of Scope Changes check ✅ Passed The remaining changes support the chat feature, with no clearly unrelated code paths or broad refactors beyond the chat/security work.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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

Comment @coderabbitai help to get the list of available commands.

@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: 9

🧹 Nitpick comments (5)
server/routes/chat.js (1)

26-53: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

No validation that providerId is actually a provider.

Any authenticated user id can be passed as providerId, letting a patient open a "provider chat" with another regular patient. Consider validating User.findOne({ _id: providerId, role: { $in: ['doctor', 'provider'] } }) before creating the conversation.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@server/routes/chat.js` around lines 26 - 53, Update the conversations route
to validate providerId with User.findOne before querying or creating a
conversation, requiring the referenced user’s role to be doctor or provider.
Return an appropriate client error when no qualifying provider exists, and only
proceed with the existing Conversation flow after validation succeeds.
server/index.js (1)

181-186: 🔒 Security & Privacy | 🔵 Trivial

Static analysis flags plain http — not a new regression here.

http.createServer(app) mirrors the prior implicit app.listen() behavior (Express creates an HTTP server internally either way); this diff doesn't change the transport. Worth confirming TLS termination happens at a reverse proxy/load balancer in production, since chat payloads carry PHI in transit.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@server/index.js` around lines 181 - 186, Confirm that production TLS
termination is handled by the configured reverse proxy or load balancer before
exposing the new http.createServer(app) setup in the main startup block; retain
the existing HTTP transport unless deployment configuration requires changing
it.

Source: Linters/SAST tools

server/models/Message.js (1)

4-4: 🔒 Security & Privacy | 🔵 Trivial | 🏗️ Heavy lift

Switch server/models/Message.js to authenticated encryption
aes-256-cbc only provides confidentiality; the current iv:ciphertext format has no integrity check, so tampering can go unnoticed. Use aes-256-gcm and store the auth tag with the IV, or add an HMAC if CBC must stay.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@server/models/Message.js` at line 4, Update the encryption implementation in
server/models/Message.js, including the ALGORITHM constant and corresponding
encrypt/decrypt logic, to use authenticated encryption with aes-256-gcm. Store
and parse the authentication tag alongside the IV and ciphertext, and ensure
decryption verifies the tag before returning plaintext.
src/services/chatService.js (1)

5-37: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Reduce duplication with a shared axios instance.

Every exported function re-derives headers and repeats the base URL. Consider a single axios.create() instance with a request interceptor that attaches the Authorization header, so future changes (e.g., token refresh, 401 handling) live in one place.

♻️ Suggested refactor
-const API_URL = process.env.REACT_APP_API_URL || 'http://localhost:5000';
-
-const getAuthHeaders = () => {
-  const token = localStorage.getItem('token');
-  return {
-    headers: {
-      Authorization: `Bearer ${token}`
-    }
-  };
-};
-
-export const getConversations = async () => {
-  const response = await axios.get(`${API_URL}/api/chat/conversations`, getAuthHeaders());
-  return response.data;
-};
+const API_URL = process.env.REACT_APP_API_URL || 'http://localhost:5000';
+
+const chatClient = axios.create({ baseURL: `${API_URL}/api/chat` });
+
+chatClient.interceptors.request.use((config) => {
+  const token = localStorage.getItem('token');
+  if (token) config.headers.Authorization = `Bearer ${token}`;
+  return config;
+});
+
+export const getConversations = async () => {
+  const response = await chatClient.get('/conversations');
+  return response.data;
+};
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/services/chatService.js` around lines 5 - 37, Refactor the chat service
around a shared axios instance created with API_URL as its base URL. Move token
retrieval and Authorization attachment into that instance’s request interceptor,
remove getAuthHeaders, and update getConversations, startConversation,
getMessages, sendMessage, and getProviders to use the shared instance without
repeating the base URL or config.
src/pages/SecureChat.jsx (1)

11-29: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Unused useAuth import; manual localStorage parsing duplicates auth context and the shape ambiguity.

useAuth is imported but never called, and the comment "Assuming this exists" / "for now" signals this is unfinished. Manually re-parsing localStorage.getItem('user') instead of using the existing context is likely why user?._id || user?.id has to be repeated throughout the file (lines 119, 139, 144, 150, 228) instead of relying on a single, known user shape from useAuth().

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/pages/SecureChat.jsx` around lines 11 - 29, Replace the manual
localStorage user parsing in SecureChat with the existing useAuth() hook,
removing the unused import warning and temporary comments. Use the authenticated
user returned by the context throughout the component, preserving the existing
behavior while relying on its established user shape instead of repeated
user?.id/user?._id fallbacks.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@server/index.js`:
- Around line 188-209: Secure the Socket.IO connection handlers by
authenticating each socket during the connection handshake and authorizing
conversation access before allowing room membership. Update the joinRoom handler
to verify the authenticated user participates in conversationId before
socket.join, and guard sendMessage and typing so only sockets authorized for and
joined to the target conversation can publish events; reject unauthorized
requests without broadcasting.

In `@server/models/Conversation.js`:
- Around line 1-19: The Conversation schema needs DB-enforced uniqueness for
participant pairs, and the conversation creation route must handle concurrent
duplicates. In server/models/Conversation.js lines 1-19, add a canonical sorted
participants key with a unique index; in server/routes/chat.js lines 36-46,
update the conversation.save() error handling to detect MongoDB E11000
duplicate-key errors, re-fetch the existing conversation by that key, and return
it instead of failing the request.

In `@server/models/Message.js`:
- Around line 39-64: Update the Message content encryption flow around
MessageSchema so the backend never holds or derives the decryption key and
cannot decrypt stored plaintext. Move encryption/decryption and key management
to the client-side message flow, and ensure server persistence and retrieval use
only client-encrypted content without the decryptText getter or server-side key
access.
- Around line 7-9: Update the ENCRYPTION_KEY initialization to require
process.env.ENCRYPTION_KEY and fail fast when it is unset; remove the hardcoded
fallback literal. Ensure the resulting encryption key is exactly 32 bytes long
before it is used by the Message model, rejecting invalid values during startup.

In `@server/models/User.js`:
- Around line 3-5: Replace the no-op encrypt and decrypt functions in User.js
with synchronous Node crypto-based encryption and decryption, using the
established AES-256-CBC approach from Message.js while preserving the Mongoose
getter/setter contract. Ensure twoFactorSecret values remain encrypted at rest
and decrypt correctly when read.

In `@server/routes/chat.js`:
- Line 67: Update the participant authorization checks in the chat read and send
handlers to compare normalized identifier values rather than ObjectId instances
with the string userId. Apply the same ObjectId-to-string comparison in both
conversation.participants checks while preserving the existing 403 behavior for
non-participants.

In `@src/pages/SecureChat.jsx`:
- Around line 77-82: Reset the partnerTyping state when activeConversation
changes in the useEffect that joins the room and fetches messages, ensuring a
conversation switch immediately clears any stale typing indicator before
handling events for the new room.
- Around line 59-75: Update the useEffect around the socket listeners to
register stable newMessage and userTyping handlers, filter newMessage so only
messages whose conversationId matches activeConversation._id are appended, and
return a cleanup function that removes both handlers with socket.off. Preserve
the existing participant check for typing updates and handle the absent active
conversation safely.
- Around line 50-57: Update the SecureChat socket initialization to pass the
current JWT through Socket.IO’s auth handshake when calling io(API_URL). In the
server’s io.use middleware, validate that token and reject unauthenticated
connections before joinRoom or sendMessage handlers can run; preserve
authenticated room and messaging behavior.

---

Nitpick comments:
In `@server/index.js`:
- Around line 181-186: Confirm that production TLS termination is handled by the
configured reverse proxy or load balancer before exposing the new
http.createServer(app) setup in the main startup block; retain the existing HTTP
transport unless deployment configuration requires changing it.

In `@server/models/Message.js`:
- Line 4: Update the encryption implementation in server/models/Message.js,
including the ALGORITHM constant and corresponding encrypt/decrypt logic, to use
authenticated encryption with aes-256-gcm. Store and parse the authentication
tag alongside the IV and ciphertext, and ensure decryption verifies the tag
before returning plaintext.

In `@server/routes/chat.js`:
- Around line 26-53: Update the conversations route to validate providerId with
User.findOne before querying or creating a conversation, requiring the
referenced user’s role to be doctor or provider. Return an appropriate client
error when no qualifying provider exists, and only proceed with the existing
Conversation flow after validation succeeds.

In `@src/pages/SecureChat.jsx`:
- Around line 11-29: Replace the manual localStorage user parsing in SecureChat
with the existing useAuth() hook, removing the unused import warning and
temporary comments. Use the authenticated user returned by the context
throughout the component, preserving the existing behavior while relying on its
established user shape instead of repeated user?.id/user?._id fallbacks.

In `@src/services/chatService.js`:
- Around line 5-37: Refactor the chat service around a shared axios instance
created with API_URL as its base URL. Move token retrieval and Authorization
attachment into that instance’s request interceptor, remove getAuthHeaders, and
update getConversations, startConversation, getMessages, sendMessage, and
getProviders to use the shared instance without repeating the base URL or
config.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 7d4984b9-9dea-4311-b21d-8e601571d490

📥 Commits

Reviewing files that changed from the base of the PR and between 80dcf6a and 237eb95.

⛔ Files ignored due to path filters (2)
  • package-lock.json is excluded by !**/package-lock.json
  • server/package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (12)
  • package.json
  • server/index.js
  • server/models/Conversation.js
  • server/models/Message.js
  • server/models/User.js
  • server/package.json
  • server/routes/auth.js
  • server/routes/chat.js
  • src/App.jsx
  • src/i18n/locales/en.json
  • src/pages/SecureChat.jsx
  • src/services/chatService.js
💤 Files with no reviewable changes (1)
  • server/routes/auth.js

Comment thread server/index.js
Comment on lines +1 to +19
const mongoose = require('mongoose');

const ConversationSchema = new mongoose.Schema({
participants: [{
type: mongoose.Schema.Types.ObjectId,
ref: 'User',
required: true
}],
lastMessage: {
type: mongoose.Schema.Types.ObjectId,
ref: 'Message'
},
updatedAt: {
type: Date,
default: Date.now
}
});

module.exports = mongoose.model('Conversation', ConversationSchema);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Concurrent POST /conversations requests can create duplicate conversations for the same participant pair. The findOne-then-save pattern in chat.js has no DB-level backstop (no unique index in the schema), so a race under concurrent requests can produce two conversations for the same two users. Per repo learning, this deployment is a non-replica-set MongoDB (no multi-document transactions), so the fix should be a non-transactional, DB-enforced uniqueness constraint rather than a transaction.

  • server/models/Conversation.js#L1-L19: add a canonical sorted-participants key field (e.g., participantsKey: sorted(participants).join(',')) with a unique index, so MongoDB itself rejects duplicate pairs.
  • server/routes/chat.js#L36-L46: on conversation.save(), catch the resulting E11000 duplicate-key error and re-fetch/return the existing conversation instead of failing the request.
📍 Affects 2 files
  • server/models/Conversation.js#L1-L19 (this comment)
  • server/routes/chat.js#L36-L46
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@server/models/Conversation.js` around lines 1 - 19, The Conversation schema
needs DB-enforced uniqueness for participant pairs, and the conversation
creation route must handle concurrent duplicates. In
server/models/Conversation.js lines 1-19, add a canonical sorted participants
key with a unique index; in server/routes/chat.js lines 36-46, update the
conversation.save() error handling to detect MongoDB E11000 duplicate-key
errors, re-fetch the existing conversation by that key, and return it instead of
failing the request.

Source: Learnings

Comment thread server/models/Message.js Outdated
Comment thread server/models/Message.js
Comment on lines +39 to +64
const MessageSchema = new mongoose.Schema({
conversationId: {
type: mongoose.Schema.Types.ObjectId,
ref: 'Conversation',
required: true
},
sender: {
type: mongoose.Schema.Types.ObjectId,
ref: 'User',
required: true
},
content: {
type: String,
required: true,
get: decryptText,
set: encryptText
},
read: {
type: Boolean,
default: false
},
createdAt: {
type: Date,
default: Date.now
}
}, { toJSON: { getters: true }, toObject: { getters: true } });

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔒 Security & Privacy | 🟠 Major | 🏗️ Heavy lift

Server-side encryption, not end-to-end encryption.

Issue #218 calls for preventing the backend from storing decrypted content via end-to-end encryption. This implementation keeps the symmetric key server-side and decrypts on every read (via the schema getter), so the backend can always read plaintext — it's encryption-at-rest, not E2E. If E2E is a hard requirement, the key should be held by the client(s), not derived server-side from an env var.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@server/models/Message.js` around lines 39 - 64, Update the Message content
encryption flow around MessageSchema so the backend never holds or derives the
decryption key and cannot decrypt stored plaintext. Move encryption/decryption
and key management to the client-side message flow, and ensure server
persistence and retrieval use only client-encrypted content without the
decryptText getter or server-side key access.

Comment thread server/models/User.js Outdated
Comment thread server/routes/chat.js Outdated
Comment thread src/pages/SecureChat.jsx Outdated
Comment thread src/pages/SecureChat.jsx
Comment thread src/pages/SecureChat.jsx

@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: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/pages/SecureChat.jsx (1)

89-108: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Race conditions on state updates after async operations.

These locations update state after an await without verifying if the user has switched conversations or if the component is still in the same state, which can lead to rendering the wrong chat history or overwriting concurrent updates.

  • src/pages/SecureChat.jsx#L89-L108: Inline fetchMessages into the effect and use an isActive boolean flag to prevent overwriting the chat view if the active conversation changes during the fetch.
  • src/pages/SecureChat.jsx#L113-L116: Use a functional state update (setConversations(prev => ...)) to prevent losing concurrent list updates due to the stale conversations closure.
  • src/pages/SecureChat.jsx#L136-L139: In the optimistic update, use the functional state updater to verify that the view hasn't switched to a different conversation (e.g., checking if (prev.some(m => m.conversationId && m.conversationId !== savedMessage.conversationId)) return prev;).
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/pages/SecureChat.jsx` around lines 89 - 108, In src/pages/SecureChat.jsx
lines 89-108, inline fetchMessages within the activeConversation effect and
guard setMessages with an isActive flag that cleanup clears when the
conversation or component changes. At lines 113-116, update conversations
through a functional setConversations(prev => ...) updater. At lines 136-139,
use a functional messages updater that returns the previous state when it
detects the view has switched to a different conversation, otherwise applying
the optimistic update.
♻️ Duplicate comments (1)
src/pages/SecureChat.jsx (1)

63-66: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Prevent background messages from appending when no conversation is active.

Because of the short-circuit && evaluation, if activeConversation is null (e.g., the user is on the "Select a provider" screen), the condition evaluates to null and the function proceeds to append the background message to the empty messages view array.

Ensure the message is rejected if there is no active conversation.

🐛 Proposed fix
     const handleNewMessage = (message) => {
       // Only append if the message is for the currently active conversation
-      if (activeConversation && message.conversationId !== activeConversation._id) return;
+      if (!activeConversation || message.conversationId !== activeConversation._id) return;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/pages/SecureChat.jsx` around lines 63 - 66, Update handleNewMessage so it
returns immediately when activeConversation is absent or when
message.conversationId differs from activeConversation._id; only append messages
for an active matching conversation.
🧹 Nitpick comments (3)
src/pages/SecureChat.jsx (2)

23-25: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Remove unused state variable.

The isTyping state variable and its setter are declared but never used in the component (the socket typing emits use boolean literals).

♻️ Proposed refactor
   const [loading, setLoading] = useState(true);
-  const [isTyping, setIsTyping] = useState(false);
   const [partnerTyping, setPartnerTyping] = useState(false);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/pages/SecureChat.jsx` around lines 23 - 25, Remove the unused isTyping
state declaration and its setIsTyping setter from the SecureChat component,
leaving the loading and partnerTyping state unchanged.

55-57: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Clear the typing timeout on unmount.

Failing to clear the timeout when the component unmounts can cause memory leaks or attempt to execute state updates and emit events on a disconnected socket.

♻️ Proposed refactor
     return () => {
       if (newSocket) newSocket.disconnect();
+      if (typingTimeoutRef.current) clearTimeout(typingTimeoutRef.current);
     };
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/pages/SecureChat.jsx` around lines 55 - 57, Update the cleanup function
in the SecureChat component’s effect to clear the typing timeout on unmount, in
addition to disconnecting newSocket. Reuse the existing timeout reference and
guard the cleanup so no callback can update state or emit through the
disconnected socket.
server/models/Message.js (1)

17-48: 📐 Maintainability & Code Quality | 🔵 Trivial | 🏗️ Heavy lift

Duplicated AES-GCM encrypt/decrypt across models — extract a shared util. The key setup, encrypt/decrypt, IV/tag handling, and packed-string format are copy-pasted between the two models, so they can silently drift (they already differ: Message throws on a bad key, User no-ops). Consolidate into one module (e.g. reinstate server/utils/encryption) to enforce a single, consistent policy.

  • server/models/Message.js#L17-L48: replace the local encryptText/decryptText with the shared helper.
  • server/models/User.js#L17-L44: replace the local encrypt/decrypt with the same shared helper (also resolves the fail-fast inconsistency).
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@server/models/Message.js` around lines 17 - 48, Extract the shared AES-GCM
key setup, encryption/decryption, IV and authentication-tag handling, and
packed-string format into a single utility module. In server/models/Message.js
lines 17-48, replace local encryptText and decryptText with imports and calls to
that helper; in server/models/User.js lines 17-44, replace local encrypt and
decrypt likewise. Ensure both models use the same failure behavior and
encryption policy defined by the shared utility.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@server/index.js`:
- Around line 191-203: Update the Socket.IO authentication middleware callback
around jwt.verify to reject decoded tokens where isTemp is true, matching the
existing authMiddleware behavior. Return an authentication error through next
for temporary tokens before assigning socket.user or allowing the handshake to
continue; preserve valid non-temporary token handling.

In `@server/models/Message.js`:
- Around line 30-35: Restore legacy aes-256-cbc decryption for the 2-part
iv:ciphertext format instead of returning the raw value in the Message getter.
Update the corresponding 2-part handling in server/models/Message.js (lines
30-35) and server/models/User.js (lines 27-31) to decrypt existing records using
the established CBC logic, or migrate those rows so both models continue reading
legacy messages correctly.

---

Outside diff comments:
In `@src/pages/SecureChat.jsx`:
- Around line 89-108: In src/pages/SecureChat.jsx lines 89-108, inline
fetchMessages within the activeConversation effect and guard setMessages with an
isActive flag that cleanup clears when the conversation or component changes. At
lines 113-116, update conversations through a functional setConversations(prev
=> ...) updater. At lines 136-139, use a functional messages updater that
returns the previous state when it detects the view has switched to a different
conversation, otherwise applying the optimistic update.

---

Duplicate comments:
In `@src/pages/SecureChat.jsx`:
- Around line 63-66: Update handleNewMessage so it returns immediately when
activeConversation is absent or when message.conversationId differs from
activeConversation._id; only append messages for an active matching
conversation.

---

Nitpick comments:
In `@server/models/Message.js`:
- Around line 17-48: Extract the shared AES-GCM key setup,
encryption/decryption, IV and authentication-tag handling, and packed-string
format into a single utility module. In server/models/Message.js lines 17-48,
replace local encryptText and decryptText with imports and calls to that helper;
in server/models/User.js lines 17-44, replace local encrypt and decrypt
likewise. Ensure both models use the same failure behavior and encryption policy
defined by the shared utility.

In `@src/pages/SecureChat.jsx`:
- Around line 23-25: Remove the unused isTyping state declaration and its
setIsTyping setter from the SecureChat component, leaving the loading and
partnerTyping state unchanged.
- Around line 55-57: Update the cleanup function in the SecureChat component’s
effect to clear the typing timeout on unmount, in addition to disconnecting
newSocket. Reuse the existing timeout reference and guard the cleanup so no
callback can update state or emit through the disconnected socket.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: f87c7c45-ff14-4d28-b235-e0447e7d1219

📥 Commits

Reviewing files that changed from the base of the PR and between 237eb95 and 9d49538.

📒 Files selected for processing (7)
  • server/index.js
  • server/models/Conversation.js
  • server/models/Message.js
  • server/models/User.js
  • server/routes/chat.js
  • src/pages/SecureChat.jsx
  • src/services/chatService.js
🚧 Files skipped from review as they are similar to previous changes (3)
  • server/models/Conversation.js
  • src/services/chatService.js
  • server/routes/chat.js

Comment thread server/index.js
Comment thread server/models/Message.js Outdated

@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: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/pages/SecureChat.jsx (1)

74-78: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Verify conversationId to prevent cross-chat typing indicators.

The handleUserTyping event listener does not verify the conversationId. Since the client joins multiple rooms over time without leaving them (line 105), a provider typing in a background chat could erroneously trigger the "Typing..." indicator in the currently active chat if they happen to also be a participant in it.

🐛 Proposed fix
-   const handleUserTyping = ({ senderId, isTyping }) => {
-     if (activeConversation && activeConversation.participants.some(p => p._id === senderId)) {
+   const handleUserTyping = ({ conversationId, senderId, isTyping }) => {
+     if (
+       activeConversation && 
+       conversationId === activeConversation._id &&
+       activeConversation.participants.some(p => p._id === senderId)
+     ) {
        setPartnerTyping(isTyping);
      }
    };
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/pages/SecureChat.jsx` around lines 74 - 78, Update handleUserTyping to
accept the event’s conversationId and require it to match the currently active
conversation’s identifier before calling setPartnerTyping; retain the existing
participant check so typing indicators only update for the active chat.
🧹 Nitpick comments (3)
server/utils/encryption.js (1)

6-6: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Remove unused constant.

CBC_IV_LENGTH is defined but never used in this module.

♻️ Proposed fix
-const CBC_IV_LENGTH = 16;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@server/utils/encryption.js` at line 6, Remove the unused CBC_IV_LENGTH
constant declaration from the encryption module, leaving the remaining
encryption logic unchanged.
src/pages/SecureChat.jsx (2)

174-177: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Use optional chaining for defensive programming.

If the backend ever returns a conversation without a populated participants array, calling .find() on it will throw a fatal TypeError and crash the component.

🛡️ Proposed refactor
  const getPartner = (conv) => {
    const currentId = user?._id || user?.id;
-   return conv.participants.find(p => p._id !== currentId) || conv.participants[0];
+   return conv?.participants?.find(p => p._id !== currentId) || conv?.participants?.[0];
  };
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/pages/SecureChat.jsx` around lines 174 - 177, Update getPartner to safely
handle conversations whose participants value is missing before calling find,
using optional chaining while preserving the existing fallback to the first
participant when the array is available.

63-72: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Update the sidebar when new messages arrive.

Currently, handleNewMessage ignores messages for non-active conversations and does not update the conversations list. This means the "Providers & Chats" sidebar won't reflect new messages or update the last message preview until the page is refreshed.

Since the client remains subscribed to previously joined rooms, you can enhance the user experience by updating the conversations state to show the latest message preview in the sidebar dynamically.

♻️ Proposed refactor
    const handleNewMessage = (message) => {
+     // Update the last message in the sidebar for the corresponding conversation
+     setConversations(prev => prev.map(conv => 
+       conv._id === message.conversationId 
+         ? { ...conv, lastMessage: message }
+         : conv
+     ));
+
      // Only append if the message is for the currently active conversation
      if (!activeConversation || message.conversationId !== activeConversation._id) return;

      setMessages((prevMessages) => {
        // Prevent duplicates
        if (prevMessages.some(m => m._id === message._id)) return prevMessages;
        return [...prevMessages, message];
      });
    };
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/pages/SecureChat.jsx` around lines 63 - 72, Update handleNewMessage in
SecureChat to update the conversations state for every incoming message,
including messages from non-active conversations, by replacing the matching
conversation’s latest-message preview while preserving the existing
conversations order and entries. Keep the active-conversation message append and
duplicate prevention behavior unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@server/utils/encryption.js`:
- Around line 10-16: Restore the previous crypto.scryptSync-based derivation in
the ENCRYPTION_KEY initialization, using the existing environment value and salt
to produce a guaranteed 32-byte key. Remove the direct UTF-8 length validation
and assignment so legacy AES-256-CBC and AES-256-GCM data continue using the
same derived key.

In `@src/pages/SecureChat.jsx`:
- Around line 147-151: Fix the stale conversation check in the setMessages
updater within handleSendMessage by adding an active conversation ID ref,
synchronizing it via useEffect whenever activeConversation changes, and
comparing activeConvIdRef.current with savedMessage.conversationId. Keep the
duplicate-message guard and existing append behavior unchanged.

---

Outside diff comments:
In `@src/pages/SecureChat.jsx`:
- Around line 74-78: Update handleUserTyping to accept the event’s
conversationId and require it to match the currently active conversation’s
identifier before calling setPartnerTyping; retain the existing participant
check so typing indicators only update for the active chat.

---

Nitpick comments:
In `@server/utils/encryption.js`:
- Line 6: Remove the unused CBC_IV_LENGTH constant declaration from the
encryption module, leaving the remaining encryption logic unchanged.

In `@src/pages/SecureChat.jsx`:
- Around line 174-177: Update getPartner to safely handle conversations whose
participants value is missing before calling find, using optional chaining while
preserving the existing fallback to the first participant when the array is
available.
- Around line 63-72: Update handleNewMessage in SecureChat to update the
conversations state for every incoming message, including messages from
non-active conversations, by replacing the matching conversation’s
latest-message preview while preserving the existing conversations order and
entries. Keep the active-conversation message append and duplicate prevention
behavior unchanged.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: cb501b30-8a32-430b-a962-032c11b9f1b2

📥 Commits

Reviewing files that changed from the base of the PR and between 9d49538 and 9313968.

📒 Files selected for processing (5)
  • server/index.js
  • server/models/Message.js
  • server/routes/chat.js
  • server/utils/encryption.js
  • src/pages/SecureChat.jsx
🚧 Files skipped from review as they are similar to previous changes (2)
  • server/routes/chat.js
  • server/index.js

Comment thread server/utils/encryption.js Outdated
Comment thread src/pages/SecureChat.jsx
@Dev1822
Dev1822 force-pushed the feature/secure-chat branch from 9313968 to 21beb82 Compare July 20, 2026 16:11

@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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/pages/SecureChat.jsx (1)

160-173: 🚀 Performance & Scalability | 🟠 Major | ⚡ Quick win

Prevent WebSocket spam by tracking the typing state.

Currently, isTyping: true is emitted on every single keystroke because there is no check to see if the user is already considered typing. This will flood the WebSocket server with redundant messages and degrade performance.

You can use the existing typingTimeoutRef to determine if a typing session is already active, emitting isTyping: true only on the first keystroke, and ensuring the ref is reset to null when the timeout fires or the message is sent.

⚡ Proposed fix
   const handleTyping = (e) => {
     setNewMessage(e.target.value);
     
     if (socket && activeConversation) {
-      socket.emit('typing', { conversationId: activeConversation._id, senderId: user?._id || user?.id, isTyping: true });
-      
-      if (typingTimeoutRef.current) clearTimeout(typingTimeoutRef.current);
+      if (!typingTimeoutRef.current) {
+        socket.emit('typing', { conversationId: activeConversation._id, senderId: user?._id || user?.id, isTyping: true });
+      } else {
+        clearTimeout(typingTimeoutRef.current);
+      }
       
       typingTimeoutRef.current = setTimeout(() => {
         socket.emit('typing', { conversationId: activeConversation._id, senderId: user?._id || user?.id, isTyping: false });
+        typingTimeoutRef.current = null;
       }, 2000);
     }
   };

Additionally, ensure you reset the ref when a message is explicitly sent so the next keystroke correctly starts a new typing session. In handleSendMessage:

       // Stop typing
       socket.emit('typing', { conversationId: activeConversation._id, senderId: user?._id || user?.id, isTyping: false });
-      if (typingTimeoutRef.current) clearTimeout(typingTimeoutRef.current);
+      if (typingTimeoutRef.current) {
+        clearTimeout(typingTimeoutRef.current);
+        typingTimeoutRef.current = null;
+      }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/pages/SecureChat.jsx` around lines 160 - 173, Update handleTyping to emit
isTyping: true only when typingTimeoutRef.current is inactive, while continuing
to reset and reschedule the timeout for subsequent keystrokes; set the ref to
null when the timeout emits isTyping: false. Also update handleSendMessage to
clear the active typing timeout and reset typingTimeoutRef.current to null so
the next keystroke starts a new typing session.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/pages/SecureChat.jsx`:
- Around line 103-107: Update the conversation-switching logic in SecureChat
around the activeConversation/socket joinRoom block to clear the messages state
immediately before joining the new room and loading messages. Preserve the
existing typing reset, room emission, and loadMessages flow.

---

Outside diff comments:
In `@src/pages/SecureChat.jsx`:
- Around line 160-173: Update handleTyping to emit isTyping: true only when
typingTimeoutRef.current is inactive, while continuing to reset and reschedule
the timeout for subsequent keystrokes; set the ref to null when the timeout
emits isTyping: false. Also update handleSendMessage to clear the active typing
timeout and reset typingTimeoutRef.current to null so the next keystroke starts
a new typing session.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: ba9741b8-b8ce-470c-95a5-161213908504

📥 Commits

Reviewing files that changed from the base of the PR and between 9313968 and 21beb82.

📒 Files selected for processing (5)
  • server/index.js
  • server/models/Message.js
  • server/routes/chat.js
  • server/utils/encryption.js
  • src/pages/SecureChat.jsx
🚧 Files skipped from review as they are similar to previous changes (4)
  • server/utils/encryption.js
  • server/models/Message.js
  • server/routes/chat.js
  • server/index.js

Comment thread src/pages/SecureChat.jsx
@Dev1822
Dev1822 force-pushed the feature/secure-chat branch 2 times, most recently from ece4b9a to e01c62c Compare July 22, 2026 18:52

@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

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/pages/SecureChat.jsx`:
- Around line 110-112: Update the getMessages flow in SecureChat so the fetched
history is merged with the current messages instead of replacing them,
preserving any socket-delivered messages received while loading. Deduplicate the
combined messages using the existing message identity field, and keep the update
guarded by isActive.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: ee2d53e0-e5d5-40c4-9601-2d158c1906b5

📥 Commits

Reviewing files that changed from the base of the PR and between 21beb82 and e01c62c.

📒 Files selected for processing (5)
  • server/index.js
  • server/models/Message.js
  • server/routes/chat.js
  • server/utils/encryption.js
  • src/pages/SecureChat.jsx
🚧 Files skipped from review as they are similar to previous changes (4)
  • server/models/Message.js
  • server/utils/encryption.js
  • server/routes/chat.js
  • server/index.js

Comment thread src/pages/SecureChat.jsx Outdated
@Dev1822
Dev1822 force-pushed the feature/secure-chat branch from e01c62c to c8ec936 Compare July 22, 2026 18:58
@sonarqubecloud

Copy link
Copy Markdown

Quality Gate Failed Quality Gate failed

Failed conditions
E Security Rating on New Code (required ≥ A)
D Reliability Rating on New Code (required ≥ A)

See analysis details on SonarQube Cloud

💡 Need a hand with PR review? Try Gitar by Sonar!

@github-actions github-actions Bot added bug Something isn't working documentation Improvements or additions to documentation enhancement New feature or request security ui/ux labels Jul 27, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working documentation Improvements or additions to documentation enhancement New feature or request security ui/ux

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[FEATURE] Secure Chat with Healthcare Providers

1 participant