Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions backend/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,5 @@ GOOGLE_CLIENT_ID=your_google_client_id
VAPID_PUBLIC_KEY=your_vapid_public_key
VAPID_PRIVATE_KEY=your_vapid_private_key
VAPID_SUBJECT=mailto:your@email.com
EMAIL=mailto:your@email.com
EMAIL_PASSWORD=vzktivbgeaqcdczr

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Replace the example password with a placeholder.

vzktivbgeaqcdczr looks like a real app password, not a dummy value. Keeping secret-looking credentials in .env.example normalizes committing secrets and risks accidental reuse or leakage.

Suggested change
-EMAIL_PASSWORD=vzktivbgeaqcdczr
+EMAIL_PASSWORD=your_email_app_password
📝 Committable suggestion

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

Suggested change
EMAIL_PASSWORD=vzktivbgeaqcdczr
EMAIL_PASSWORD=your_email_app_password
🧰 Tools
🪛 dotenv-linter (4.0.0)

[warning] 13-13: [UnorderedKey] The EMAIL_PASSWORD key should go before the GOOGLE_CLIENT_ID key

(UnorderedKey)

🤖 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 `@backend/.env.example` at line 13, The EMAIL_PASSWORD value in the .env
example is a real-looking secret; replace the current value for the
EMAIL_PASSWORD entry with a non-sensitive placeholder (e.g.,
EMAIL_PASSWORD=your_email_password_here or EMAIL_PASSWORD=CHANGE_ME) so the
example no longer contains secret-looking credentials and clearly signals that
users must supply their own secret.

64 changes: 64 additions & 0 deletions backend/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions backend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
"dependencies": {
"bcryptjs": "^3.0.3",
"cloudinary": "^2.10.0",
"compression": "^1.8.1",
"cookie-parser": "^1.4.7",
"cors": "^2.8.6",
"dotenv": "^17.4.2",
Expand Down
46 changes: 46 additions & 0 deletions backend/src/controllers/user.controller.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import User from "../models/user.model.js";
import { broadcastStatusMoodUpdate } from "../lib/socket.js";
import { catchAsync } from "../lib/utils.js";
import Message from "../models/message.model.js";
import bcrypt from "bcryptjs"

const ALLOWED_STATUS_MOODS = new Set([
"coding",
Expand Down Expand Up @@ -42,3 +44,47 @@ export const updateStatusMood = catchAsync(async (req, res) => {

res.status(200).json(user);
});


export const deleteProfile = catchAsync(async (req, res) => {
const userId = req.userId;
const { password } = req.body;

const user = await User.findById(userId);

if (!user) {
return res.status(404).json({
success: false,
message: "User not found",
});
}

const isMatch = await bcrypt.compare(password, user.password);
Comment on lines +51 to +62

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
echo "User schema fields related to local-vs-OAuth auth:"
sed -n '1,80p' backend/src/models/user.model.js

echo
echo "All bcrypt.compare call sites:"
rg -n -C2 'bcrypt\.compare\s*\(' backend/src

echo
echo "Any Google/OAuth-related auth paths:"
rg -n -C2 'googleId|google' backend/src

Repository: UTKARSHH20/fullstack-chat-app

Length of output: 7661


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "Locate deleteProfile in user.controller.js:"
rg -n "deleteProfile" backend/src/controllers/user.controller.js

echo
echo "Show deleteProfile implementation (around the reported lines 51-62):"
sed -n '1,140p' backend/src/controllers/user.controller.js | nl -ba | sed -n '35,120p'

echo
echo "Search for profile image / Cloudinary cleanup across backend/src:"
rg -n "cloudinary|profilePicture|XSRF-TOKEN|jwt|delete.*profile|remove.*image" backend/src -S

echo
echo "Search for message deletion logic (senderId/receiverId) specifically:"
rg -n "senderId|receiverId|deleteMany" backend/src/controllers backend/src/models -S

Repository: UTKARSHH20/fullstack-chat-app

Length of output: 313


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "Locate deleteProfile in user.controller.js:"
rg -n "export const deleteProfile" backend/src/controllers/user.controller.js

echo
echo "Show deleteProfile implementation (user.controller.js, lines 1-220):"
sed -n '1,220p' backend/src/controllers/user.controller.js | cat -n | sed -n '35,190p'

echo
echo "Search for profile image / Cloudinary cleanup across backend/src:"
rg -n "cloudinary|profilePicture|XSRF-TOKEN|jwt|delete.*profile|remove.*image|destroy.*image" backend/src -S

echo
echo "Search for message deletion logic (senderId/receiverId) specifically:"
rg -n "senderId|receiverId|deleteMany" backend/src/controllers backend/src/models -S

Repository: UTKARSHH20/fullstack-chat-app

Length of output: 14302


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "Inspect full user.model.js for any hooks (up to 240 lines):"
sed -n '1,240p' backend/src/models/user.model.js | cat -n

echo
echo "Search for Cloudinary destroy usage related to profilePicture/user deletion:"
rg -n "profilePicture.*destroy|destroy.*profilePicture|destroy.*user|on\\(\"delete|pre\\(\"delete|post\\(\"delete" backend/src -S

Repository: UTKARSHH20/fullstack-chat-app

Length of output: 1871


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "Search for profile-picture update/delete and Cloudinary destroy in auth/user-related controllers:"
rg -n "updateProfilePicture|profilePicture.*(upload|destroy|delete)|destroy\\(" backend/src/controllers backend/src/services backend/src/lib -S

echo
echo "Search for any 'delete user' helpers or routes besides deleteProfile:"
rg -n "deleteProfile|findByIdAndDelete\\(|remove\\(\\)|deleteOne\\(|deleteMany\\(\\)" backend/src -S

Repository: UTKARSHH20/fullstack-chat-app

Length of output: 1425


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "Show delete-account route middleware stack:"
sed -n '1,120p' backend/src/routes/user.route.js | cat -n

echo
echo "Show validateLogin / validateSignup behavior (if present):"
rg -n "export const validateLogin|validateLogin|export const validateSignup|validateSignup" backend/src/middleware/validate.js backend/src/middleware -S
sed -n '1,220p' backend/src/middleware/validate.js | cat -n

echo
echo "Inspect auth.controller.js login section for how bcrypt.compare inputs are handled:"
rg -n "export const login|bcrypt\\.compare" backend/src/controllers/auth.controller.js
sed -n '40,140p' backend/src/controllers/auth.controller.js | cat -n

echo
echo "Inspect updateProfilePicture to see what Cloudinary fields are stored (secure_url vs public_id):"
sed -n '160,260p' backend/src/controllers/auth.controller.js | cat -n

Repository: UTKARSHH20/fullstack-chat-app

Length of output: 11395


Guard password confirmation input and passwordless accounts before bcrypt.compare in deleteProfile.

  • deleteProfile calls bcrypt.compare(password, user.password) without validating that password is a string or that user.password is present; User.password defaults to null for OAuth accounts, and this path is currently missing the same guard used by login.
  • deleteProfile performs Message.deleteMany(...) and User.findByIdAndDelete(...) as separate operations; use a MongoDB transaction/session to avoid partial deletion.
  • No Cloudinary/profile-picture deletion runs in deleteProfile (message attachment cleanup exists elsewhere, but user profilePicture is not destroyed here).
Suggested change
 export const deleteProfile = catchAsync(async (req, res) => {
     const userId = req.userId;
     const { password } = req.body;

     const user = await User.findById(userId);

     if (!user) {
         return res.status(404).json({
             success: false,
             message: "User not found",
         });
     }
+
+    if (typeof password !== "string" || password.trim() === "") {
+        return res.status(400).json({
+            success: false,
+            message: "Password is required",
+        });
+    }
+
+    if (!user.password) {
+        return res.status(400).json({
+            success: false,
+            message: "This account does not support password confirmation",
+        });
+    }

     const isMatch = await bcrypt.compare(password, user.password);
🤖 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 `@backend/src/controllers/user.controller.js` around lines 51 - 62, In
deleteProfile, guard against missing password input and passwordless (OAuth)
accounts before calling bcrypt.compare by validating req.body.password is a
non-empty string and that user.password exists; return a 400 with a clear
message if either check fails. Wrap the deletes in a MongoDB session/transaction
(use startSession and session.withTransaction) and perform
Message.deleteMany(...) and User.findByIdAndDelete(...) inside that transaction
to avoid partial deletion. Also, if user.profilePicture (or its publicId)
exists, call the Cloudinary deletion (e.g.,
cloudinary.uploader.destroy(publicId)) as part of the same transactional flow or
immediately after successful user deletion to remove the stored profile picture.
Ensure to end/abort the session on errors and surface appropriate error
responses.


if (!isMatch) {
return res.status(400).json({
success: false,
message: "Incorrect Password",
});
}

// Delete user's messages
await Message.deleteMany({
$or: [
{ senderId: userId },
{ receiverId: userId },
],
});

// Delete user
await User.findByIdAndDelete(userId);
Comment on lines +71 to +80

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

Make the destructive deletes atomic.

If message deletion succeeds and user deletion fails, this endpoint leaves the account alive with its history already gone. Account removal needs a single transactional boundary, or a two-phase delete state, so it cannot partially apply.

🤖 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 `@backend/src/controllers/user.controller.js` around lines 71 - 80, Wrap the
two destructive operations (Message.deleteMany and User.findByIdAndDelete) in a
single transactional boundary or convert to a two-phase delete; for a
transaction use Mongoose sessions: obtain a session via mongoose.startSession(),
call session.withTransaction(async () => { await Message.deleteMany({ $or: [{
senderId: userId }, { receiverId: userId }] }, { session }); await
User.findByIdAndDelete(userId, { session }); }); and ensure you end the session
and propagate errors; alternatively implement a two-phase soft-delete by marking
User as "deleting" (e.g., userController sets user.deleting=true) and have a
background job perform the actual Message.deleteMany and final User removal to
avoid partial application.

Comment on lines +79 to +80

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
echo "Image/profile fields on the user model:"
rg -n -C2 'profile|avatar|image|publicId|cloudinary' backend/src/models/user.model.js

echo
echo "Cloudinary delete/destroy usage:"
rg -n -C2 'cloudinary|uploader\.destroy|destroy\s*\(' backend/src

echo
echo "User delete hooks or cleanup middleware:"
rg -n -C2 'pre\(|post\(|findOneAndDelete|findByIdAndDelete|deleteOne' backend/src/models backend/src

Repository: UTKARSHH20/fullstack-chat-app

Length of output: 8064


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Locate and inspect the user model + delete controller code
echo "=== backend/src/models/user.model.js ==="
ls -la backend/src/models/user.model.js
nl -ba backend/src/models/user.model.js | sed -n '1,220p'

echo
echo "=== backend/src/controllers/user.controller.js (around deletion) ==="
nl -ba backend/src/controllers/user.controller.js | sed -n '1,220p'

Repository: UTKARSHH20/fullstack-chat-app

Length of output: 257


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Search for Cloudinary deletion/destroy logic that might be used during user deletion
echo "=== Cloudinary destroy usage anywhere ==="
rg -n --glob='backend/src/**/*.js' "cloudinary\.uploader\.destroy|destroy\s*\(" backend/src | head -n 200

echo
echo "=== Any helper that extracts Cloudinary public IDs ==="
rg -n --glob='backend/src/**/*.js' "extractPublicId" backend/src | head -n 200

echo
echo "=== User deletion flow entry points ==="
rg -n --glob='backend/src/**/*.js' "findByIdAndDelete\(userId\)|deleteUser|/delete|delete.*user" backend/src/controllers backend/src/routes | head -n 200

Repository: UTKARSHH20/fullstack-chat-app

Length of output: 1210


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== User controller: find deleteProfile + updateProfilePicture ==="
rg -n "function\s+deleteProfile|const\s+deleteProfile|deleteProfile\s*=" backend/src/controllers/user.controller.js
rg -n "function\s+updateProfilePicture|const\s+updateProfilePicture|updateProfilePicture\s*=" backend/src/controllers/user.controller.js
rg -n "cloudinary|uploader\.destroy|destroy\s*\(" backend/src/controllers/user.controller.js

echo
echo "=== Print user.controller.js (top + around deleteProfile) ==="
wc -l backend/src/controllers/user.controller.js
cat -n backend/src/controllers/user.controller.js | head -n 220
echo
# best-effort: print around lines ~60-120 (where the delete snippet was)
cat -n backend/src/controllers/user.controller.js | sed -n '50,130p'

echo
echo "=== Print user.model.js ==="
wc -l backend/src/models/user.model.js
cat -n backend/src/models/user.model.js | sed -n '1,260p'

Repository: UTKARSHH20/fullstack-chat-app

Length of output: 205


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== Search for extractPublicId usage besides message.controller.js ==="
rg -n --glob='backend/src/**/*.js' "extractPublicId\s*\(" backend/src | head -n 200

echo
echo "=== Search for profilePicture update/delete logic (cloudinary destroy) ==="
rg -n --glob='backend/src/**/*.js' "profilePicture" backend/src/controllers backend/src/models | head -n 200

echo
echo "=== Search for Mongoose hooks in user.model.js ==="
rg -n "pre\s*\(|post\s*\(|schema\.(pre|post)|hooks" backend/src/models/user.model.js

Repository: UTKARSHH20/fullstack-chat-app

Length of output: 2370


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== backend/src/controllers/user.controller.js (full, with line numbers via cat -n) ==="
wc -l backend/src/controllers/user.controller.js
cat -n backend/src/controllers/user.controller.js | sed -n '1,220p'

echo
echo "=== backend/src/models/user.model.js (with line numbers) ==="
wc -l backend/src/models/user.model.js
cat -n backend/src/models/user.model.js | sed -n '1,260p'

echo
echo "=== Cloudinary usage specifically in user.controller.js ==="
rg -n --glob='backend/src/controllers/user.controller.js' "cloudinary\.uploader|uploader\.destroy|uploader\.|destroy\s*\(" backend/src/controllers/user.controller.js || true

Repository: UTKARSHH20/fullstack-chat-app

Length of output: 4946


🏁 Script executed:

#!/bin/bash
set -euo pipefail
rg -n "updateProfilePicture" backend/src/controllers/auth.controller.js
rg -n "profilePicture" backend/src/controllers/auth.controller.js | head -n 50
sed -n '150,260p' backend/src/controllers/auth.controller.js

Repository: UTKARSHH20/fullstack-chat-app

Length of output: 4660


Add Cloudinary profile-image cleanup before deleting the user document

The deleteProfile flow deletes the user via await User.findByIdAndDelete(userId) without any Cloudinary uploader.destroy call. The user model has no Mongoose delete hooks, and profilePicture is stored as upload.secure_url, so the current delete path can’t clean up the underlying Cloudinary asset (it will orphan it).

🤖 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 `@backend/src/controllers/user.controller.js` around lines 79 - 80, The delete
flow currently calls User.findByIdAndDelete(userId) and never removes the
Cloudinary asset; update the deleteProfile flow to first fetch the user (e.g.,
via User.findById or findByIdAndUpdate with return) read the profilePicture
field (upload.secure_url or ideally upload.public_id), call
cloudinary.uploader.destroy(public_id) (or parse the public_id from secure_url
if public_id is not stored), await successful destroy (handle errors/no-op if no
image), then proceed to remove the user with User.findByIdAndDelete(userId);
ensure you reference User.findByIdAndDelete, profilePicture (or
upload.public_id), and cloudinary.uploader.destroy in the change.


// Logout
res.clearCookie("jwt");
res.clearCookie("XSRF-TOKEN");

return res.status(200).json({
success: true,
message: "Account deleted successfully",
});
});
3 changes: 2 additions & 1 deletion backend/src/routes/user.route.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import express from "express";
import protectRoute from "../middleware/auth.middleware.js";
import { updateStatusMood } from "../controllers/user.controller.js";
import { deleteProfile, updateStatusMood } from "../controllers/user.controller.js";

const router = express.Router();

router.patch("/status-mood", protectRoute, updateStatusMood);
router.delete("/delete-account", protectRoute, deleteProfile);

export default router;
Loading
Loading