Skip to content
5 changes: 4 additions & 1 deletion backend/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
const express = require("express");
const cors = require("cors");
const session = require("express-session");
const dotenv = require("dotenv");
const passport = require("./middlewares/Passport");
const routes = require("./routes");
const authRoutes = require("./routes/authRoutes");
Expand All @@ -11,6 +12,8 @@ const adminRegistrationRoutes = require("./routes/adminRegistrationRoutes");

const app = express();

dotenv.config();

// Middleware setup
app.use(express.json());
app.use(cors());
Expand All @@ -28,7 +31,7 @@ app.use(passport.session());
app.use("/auth", authRoutes);
app.use("/api", routes);
app.use("/api", passwordResetRoutes);
app.use("/vpi", userRoutes);
app.use("/api/users", userRoutes);
app.use("/api/v1", adminRegistrationRoutes);

module.exports = app;
57 changes: 57 additions & 0 deletions backend/controllers/userController.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
const User = require("../models/User");

exports.getUsers = async (_, res) => {
try {
const user = await User.find();

return res.status(200).json({ user });
} catch (error) {
return res.status(500).json({ error: error.message });
}
};

exports.getUserByUsername = async (req, res) => {
try {
const { user_name } = req.params;

if (!user_name)
return res.status(400).json({ message: "User name is required" });

const user = await User.findOne({ username: user_name });

if (!user) return res.status(404).json({ message: "Account not found" });

return res.send(user);
} catch (error) {
return res.status(500).json({ error: error.message });
}
};

exports.updateUser = async (req, res) => {
try {
const { id } = req.params;
const { name, email, phone, gender, profile_picture } = req.body;

if (!name || !email || !phone || !gender || !profile_picture)
return res
.status(400)
.json({ message: "Enter the required fields to update your profile" });

const user = await User.findById(id);

if (!user) return res.status(404).json({ message: "Account not found" });

const updatedUser = await User.findByIdAndUpdate(
{ _id: id },
{ name, email, phone, gender, profile_picture },
{ new: true }
);

if (updatedUser)
return res.status(200).json({ message: "Profile updated", user: updatedUser });

return res.status(500).json({ message: "Failed to your profile" });
} catch (error) {
return res.status(500).send(error.message);
}
};
Copy link
Contributor

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Confirm authz: is this route protected and scoped to the acting user?

If there’s no auth middleware on PUT /:id, any client could update any user by ID. Ensure a token-based guard and “self or admin” authorization.

Run this to verify route protection and middleware usage:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect user routes for auth middleware on PUT /:id
rg -n -C3 -P 'router\.(put|patch)\s*\(\s*[\'"]/:id[\'"]\s*,\s*[^)]*\)' backend/routes/userRoutes.js

# Look for auth middleware imports in routes/app
rg -n -C2 -P 'require\\(.*auth.*\\)|from\\s+[\'"].*auth.*[\'"]' backend/routes/userRoutes.js backend/app.js backend/middleware

# Search for common middleware names across backend
rg -n -C2 -P '\\b(auth|isAuth|authenticate|verify(Token|JWT)|requireAuth|protect)\\b' backend

Length of output: 129


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect user routes for auth middleware on PUT /:id
rg -n -C3 -P "router\.(put|patch)\s*\(\s*['\"]/?:id['\"],\s*.*" backend/routes/userRoutes.js

# Look for auth middleware imports in routes/app and middleware directory
rg -n -C2 -P "require\(.*auth.*\)|from\s+['\"].*auth.*['\"]" backend/routes/userRoutes.js backend/app.js backend/middleware

# Search for common auth middleware names across the backend
rg -n -C2 -P "\b(auth|isAuth|authenticate|verify(Token|JWT)|requireAuth|protect)\b" backend

Length of output: 845


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "---- Directory: backend/routes ----"
ls backend/routes || true

echo "---- Partial backend/app.js ----"
sed -n '1,200p' backend/app.js || true

echo "---- Mounts in backend/app.js ----"
grep -n "app.use" -C2 backend/app.js || true

if [ -f backend/routes/index.js ]; then
  echo "---- Partial backend/routes/index.js ----"
  sed -n '1,200p' backend/routes/index.js
  echo "---- Mounts in backend/routes/index.js ----"
  grep -n "router.use" -C2 backend/routes/index.js || true
fi

Length of output: 2637


Protect updateUser endpoint with authentication and “self or admin” authorization

The PUT /api/users/:id route in backend/routes/userRoutes.js is currently unguarded—any client can invoke updateUser by supplying an ID. You must enforce both authentication and an authorization check to ensure only the user themself or an admin can update that profile.

• File: backend/routes/userRoutes.js (around line 25)
Suggested diff:

- router.put("/:id", userController.updateUser);
+ const passport = require("../middlewares/Passport");
+ const { authorizeSelfOrAdmin } = require("../middlewares/authMiddleware");
+
+ router.put(
+   "/:id",
+   passport.authenticate("jwt", { session: false }),
+   authorizeSelfOrAdmin,
+   userController.updateUser
+ );

• File: backend/middlewares/authMiddleware.js (create or update)

// Ensure the authenticated user is updating their own record or is an admin
exports.authorizeSelfOrAdmin = (req, res, next) => {
  const actingUser = req.user; // set by passport
  const targetId   = req.params.id;
  if (actingUser.id === targetId || actingUser.role === "admin") {
    return next();
  }
  return res.status(403).json({ message: "Forbidden: insufficient privileges" });
};

• Verify that your Passport JWT strategy populates req.user.id and req.user.role, and that you’re mounting passport.initialize()/passport.session() before your routes in backend/app.js.

These changes will close the security gap and ensure only authorized users can update profiles.

🤖 Prompt for AI Agents
In backend/controllers/userController.js lines 30-57: the updateUser handler is
exposed without auth/authorization; add an auth guard and a self-or-admin
authorization middleware and apply them to the PUT /api/users/:id route in
backend/routes/userRoutes.js around line 25 so only the authenticated user or an
admin can update the profile. Create or update
backend/middlewares/authMiddleware.js to export an authorizeSelfOrAdmin
middleware that reads req.user.id and req.user.role (populated by your Passport
JWT strategy) and calls next() only when req.user.id === req.params.id or
req.user.role === 'admin', otherwise respond 403. Ensure backend/app.js mounts
passport.initialize()/passport.session() and the JWT strategy populates
req.user.id and req.user.role before the routes are registered, then import and
use both the authentication middleware and authorizeSelfOrAdmin on the update
route.

12 changes: 12 additions & 0 deletions backend/models/User.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@ const userSchema = new Schema({
unique: true,
trim: true,
},
name: {
type: String,
default: null
},
email: {
type: String,
required: false,
Expand All @@ -29,6 +33,14 @@ const userSchema = new Schema({
trim: true,
match: [/^\d{10}$/, "Please enter a 10-digit phone number"],
},
gender: {
type: String,
default: null
},
profile_picture: {
type: String,
default: null
},
Comment on lines +36 to +43
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Constrain gender values and validate/sanitize profile_picture URLs; also avoid null defaults.

  • Add an enum for gender to align with the UI’s options and prevent inconsistent values.
  • Validate profile_picture as an http(s) URL and trim it.
  • Prefer empty string defaults to avoid null leaking to clients.
-  gender: {
-    type: String,
-    default: null
-  },
-  profile_picture: {
-    type: String,
-    default: null
-  },
+  gender: {
+    type: String,
+    enum: ["male", "female", "other", "prefer_not_to_say", ""],
+    default: ""
+  },
+  profile_picture: {
+    type: String,
+    trim: true,
+    match: [/^https?:\/\/.+/i, "Please enter a valid URL (http/https)"],
+    default: ""
+  },

Additionally, to prevent leaking sensitive fields (e.g., hash/salt from passport-local-mongoose, reset tokens) via GET endpoints, add a toJSON transform:

// Place near schema definition
userSchema.set("toJSON", {
  transform: (_doc, ret) => {
    delete ret.hash;
    delete ret.salt;
    delete ret.resetPasswordToken;
    delete ret.resetPasswordExpires;
    delete ret.otp;
    delete ret.otpExpires;
    return ret;
  }
});

If these fields are already excluded in controllers, ignore this. Otherwise, this guardrail is highly recommended.

role: {
type: Number,
default: 0,
Expand Down
37 changes: 21 additions & 16 deletions backend/routes/userRoutes.js
Original file line number Diff line number Diff line change
@@ -1,22 +1,27 @@
const express = require("express");
const router = express.Router();
const User = require("../models/User");
const userController = require("../controllers/userController")

router.post("/update-details", async (req, res) => {
try {
const { email, phone } = req.body;
const user = await User.findById(req.user.id);
if (user) {
user.email = email;
user.phone = phone;
await user.save();
res.status(200).send("Details updated successfully");
} else {
res.status(404).send("User not found");
}
} catch (error) {
res.status(500).send(error.message);
}
});
// router.post("/update-details", async (req, res) => {
// try {
// const { email, phone } = req.body;
// const user = await User.findById(req.user.id);
// if (user) {
// user.email = email;
// user.phone = phone;
// await user.save();
// res.status(200).send("Details updated successfully");
// } else {
// res.status(404).send("User not found");
// }
// } catch (error) {
// res.status(500).send(error.message);
// }
// });

router.get("/", userController.getUsers)
router.get("/:user_name", userController.getUserByUsername)
router.put("/:id", userController.updateUser);
Comment on lines +23 to +25
Copy link
Contributor

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Add auth and input validation; current routes risk exposing/allowing unintended data access.

  • Protect GET "/" and PUT "/:id" with authentication (and authorization if roles exist).
  • Validate params (id must be a valid ObjectId) and body fields (allow-list only: name, email, phone, gender, profile_picture).
  • Consider changing GET "/:user_name" to "/username/:user_name" to avoid ambiguity with "/:id" patterns.
-router.get("/", userController.getUsers)
-router.get("/:user_name", userController.getUserByUsername)
-router.put("/:id", userController.updateUser);
+// Example (adjust to your existing middlewares)
+// const { requireAuth } = require("../middlewares/auth");
+// const { validateUserUpdate } = require("../middlewares/validators");
+// router.use(requireAuth);
+
+router.get("/", userController.getUsers);
+router.get("/username/:user_name", userController.getUserByUsername);
+router.put("/:id", /* validateUserUpdate, */ userController.updateUser);

To verify exposure and consumers:


🏁 Script executed:

#!/bin/bash
# 1) Confirm no public routes return sensitive fields
rg -nP 'getUsers|getUserByUsername|updateUser' backend/controllers -C4

# 2) Validate all frontend calls target /api/users and not legacy /api/auth/update-details
rg -nP '/api/users|/api/auth/update-details' -C2 src

# 3) Check for any unauthenticated fetches in the app (axios without withCredentials or auth headers)
rg -nP 'axios\.(get|put|post)\(' -n src | sed -n '1,200p'

Length of output: 6771


🏁 Script executed:

#!/bin/bash
echo "=== backend/routes/userRoutes.js ==="
sed -n '1,200p' backend/routes/userRoutes.js || echo "File not found"

echo -e "\n=== backend/middlewares directory ==="
if [ -d backend/middlewares ]; then
  ls -R backend/middlewares
else
  echo "No middlewares directory."
fi

echo -e "\n=== Files with 'auth' in name in backend ==="
find backend -type f | grep -i auth || echo "No auth files found"

echo -e "\n=== Files with 'validator' in name in backend ==="
find backend -type f | grep -i validator || echo "No validator files found"

Length of output: 1586


🏁 Script executed:

#!/bin/bash
# Display auth middleware definitions
sed -n '1,200p' backend/middlewares/Passport.js || echo "File not found"

Length of output: 1580


Secure & Validate All User Routes

To prevent unauthorized access and ensure only valid data reaches your controllers, the user routes must be guarded by authentication/authorization and input validation.

Key changes required:

  • Implement and apply an authentication middleware (e.g. requireAuth using Passport.js or JWT).
  • Create request‐validation middleware to enforce a valid MongoDB ObjectId for :id and allow‐list only the safe update fields.
  • Disambiguate the username lookup route to avoid collision with the /:id route.

Areas to address:

  • backend/routes/userRoutes.js: wrap routes with auth, apply validators, rename the /:user_name path.
  • backend/middlewares/: add
    • requireAuth.js (leveraging your configured Passport strategy or JWT)
    • validators/userValidator.js exporting validateObjectIdParam and validateUserUpdate (e.g. using Joi or express-validator).

Proposed diff in backend/routes/userRoutes.js:

 const express = require("express");
 const router = express.Router();
+const passport = require("../middlewares/Passport"); // or your requireAuth wrapper
+const {
+  validateObjectIdParam,
+  validateUserUpdate
+} = require("../middlewares/validators/userValidator");
 const userController = require("../controllers/userController");

-// Public routes – currently unprotected
-router.get("/", userController.getUsers)
-router.get("/:user_name", userController.getUserByUsername)
-router.put("/:id", userController.updateUser);
+// Protect all user endpoints
+router.use(passport.authenticate("session")); // or .authenticate("jwt")
+
+// List all users (auth + optional role check)
+router.get("/", userController.getUsers);
+
+// Lookup by username (validate path to avoid collision)
+router.get(
+  "/username/:user_name",
+  validateObjectIdParam.bind(null, "user_name", { type: "username" }), // optional: enforce format
+  userController.getUserByUsername
+);
+
+// Update user by ID (auth + ID & body validation)
+router.put(
+  "/:id",
+  validateObjectIdParam("id"),
+  validateUserUpdate,
+  userController.updateUser
+);

 module.exports = router;

And scaffold new files:

  • backend/middlewares/requireAuth.js (or alias Passport.js into a requireAuth function)
  • backend/middlewares/validators/userValidator.js with:
    const { param, body, validationResult } = require("express-validator");
    const mongoose = require("mongoose");
    
    const validateObjectIdParam = (paramName) => [
      param(paramName)
        .custom((value) => mongoose.Types.ObjectId.isValid(value))
        .withMessage(`${paramName} must be a valid ObjectId`),
      (req, res, next) => {
        const errors = validationResult(req);
        if (!errors.isEmpty()) {
          return res.status(400).json({ errors: errors.array() });
        }
        next();
      },
    ];
    
    const validateUserUpdate = [
      body("name").optional().isString(),
      body("email").optional().isEmail(),
      body("phone").optional().isMobilePhone("any"),
      body("gender").optional().isIn(["male","female","other"]),
      body("profile_picture").optional().isURL(),
      (req, res, next) => {
        const errors = validationResult(req);
        if (!errors.isEmpty()) {
          return res.status(400).json({ errors: errors.array() });
        }
        next();
      },
    ];
    
    module.exports = { validateObjectIdParam, validateUserUpdate };

These refactors are mandatory to ensure your API does not expose or allow modifications of user data without proper checks.

🤖 Prompt for AI Agents
In backend/routes/userRoutes.js around lines 23 to 25, the routes are
unprotected and the username route conflicts with the id route; add
authentication and validation middlewares and disambiguate the username path:
create a requireAuth middleware (using your Passport/JWT strategy) and a
validators/userValidator.js exporting validateObjectIdParam(paramName) and
validateUserUpdate per the review, then change GET "/:user_name" to a
non-conflicting path like "/by-username/:user_name" and wrap routes as:
router.get("/", requireAuth, userController.getUsers);
router.get("/by-username/:user_name", requireAuth, /* optional validators for
username if needed */, userController.getUserByUsername); router.put("/:id",
requireAuth, validateObjectIdParam("id"), validateUserUpdate,
userController.updateUser); ensure validators return 400 on validation errors
and export the new middleware files under backend/middlewares.


module.exports = router;
85 changes: 53 additions & 32 deletions src/User/components/Navbar/UserNavbar.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,13 +27,22 @@ const UserNavbar = ({ isAdmin }) => {
const handleSearch = (e) => setSearchTerm(e.target.value);

const searchableItems = [
{ name: "Fashion & Accessories", link: "/popularCategories/fashionAccessories" },
{ name: "Printing & Stationery", link: "/popularCategories/printingStationery" },
{
name: "Fashion & Accessories",
link: "/popularCategories/fashionAccessories",
},
{
name: "Printing & Stationery",
link: "/popularCategories/printingStationery",
},
{ name: "Food & Beverages", link: "/popularCategories/foodBeverages" },
{ name: "Beauty & Wellness", link: "/popularCategories/beautyWellness" },
{ name: "Furniture & Decor", link: "/popularCategories/furnitureDecor" },
{ name: "Body Care", link: "/popularCategories/bodyCare" },
{ name: "Health Supplements", link: "/popularCategories/healthSupplements" },
{
name: "Health Supplements",
link: "/popularCategories/healthSupplements",
},
{ name: "Customized Gifts", link: "/popularCategories/customizedGifts" },
{ name: "Handmade Soaps", link: "/latestInMarket/handmadeSoaps" },
{ name: "Art Supplies", link: "/latestInMarket/artSupplies" },
Expand All @@ -47,7 +56,7 @@ const UserNavbar = ({ isAdmin }) => {

useEffect(() => {
if (searchTerm) {
const results = searchableItems.filter(item =>
const results = searchableItems.filter((item) =>
item.name.toLowerCase().includes(searchTerm.toLowerCase())
);
setSearchResults(results);
Expand Down Expand Up @@ -114,7 +123,7 @@ const UserNavbar = ({ isAdmin }) => {
{ to: "/popularCategories/furnitureDecor", text: "Furniture" },
{ to: "/popularCategories/printingStationery", text: "Stationary" },
{ to: "/popularCategories/bodyCare", text: "Body-Care" },
// { to: "/meet-the-makers", text: "Meet the Makers" },
{ to: "/meet-the-makers", text: "Meet the Makers" },
];

const handleResultClick = (link) => {
Expand All @@ -124,7 +133,7 @@ const UserNavbar = ({ isAdmin }) => {
};

return (
<nav className="bg-[#ecd5c5] shadow-lg w-full z-50 -mt-1">
<nav className="bg-[#ecd5c5] shadow-lg w-full z-5 -mt-1">
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div className="flex justify-between h-20">
<div className="flex items-center w-full">
Expand All @@ -136,7 +145,8 @@ const UserNavbar = ({ isAdmin }) => {
<Link
key={link.text}
to={link.to}
className="text-green-800 hover:text-green-500 hover:underline block px-4 py-2 font-bold text-base">
className="text-green-800 hover:text-green-500 hover:underline block px-4 py-2 font-bold text-base"
>
{link.text}
</Link>
))}
Expand All @@ -162,46 +172,55 @@ const UserNavbar = ({ isAdmin }) => {
</div>
<div className="md:block hidden">
{isLoggedIn ? (
<div className="relative flex gap-3 items-center">
<lord-icon
<div className="relative z-20">
<div
className="flex gap-3 items-center cursor-pointer"
onClick={handleDropdownToggle}
className="text-3xl cursor-pointer"
style={{
height: "40px",
width: "40px",
}}
src="https://cdn.lordicon.com/hrjifpbq.json"
trigger="hover"
colors="primary:#15803D"></lord-icon>
<span className="text-green-700 font-bold">
{username}
</span>
>
<lord-icon
className="text-3xl cursor-pointer"
style={{
height: "40px",
width: "40px",
}}
src="https://cdn.lordicon.com/hrjifpbq.json"
trigger="hover"
colors="primary:#15803D"
></lord-icon>
<span className="text-green-700 font-bold">
{username}
</span>
</div>
{showDropdown && (
<div
ref={dropdownRef}
className="absolute right-0 mt-32 w-48 bg-white rounded-md shadow-lg py-1">
className="absolute top-14 w-48 bg-white rounded-md shadow-lg py-1"
>
{isAdminT && (
<Link
to="/admin"
className="block px-4 py-2 text-gray-800 hover:bg-gray-200">
className="block px-4 py-2 text-gray-800 hover:bg-gray-200"
>
Admin Dashboard
</Link>
)}
<Link
to="/dashboard"
className="block px-4 py-2 text-gray-800 hover:bg-gray-200">
className="block px-4 py-2 text-gray-800 hover:bg-gray-200"
>
Dashboard
</Link>

<Link
to="/meet-the-makers"
className="block px-4 py-2 text-gray-800 hover:bg-gray-200"
>
Meet the Makers
</Link>
<Link
to="/meet-the-makers"
className="block px-4 py-2 text-gray-800 hover:bg-gray-200"
>
Meet the Makers
</Link>
<button
onClick={handleLogout}
className="block px-4 py-2 text-gray-800 hover:bg-gray-200 w-full text-left">
className="block px-4 py-2 text-gray-800 hover:bg-gray-200 w-full text-left"
>
Logout
</button>
</div>
Expand All @@ -216,12 +235,14 @@ const UserNavbar = ({ isAdmin }) => {
<div className="-mr-2 flex md:hidden">
<button
onClick={toggleNavbar}
className="inline-flex items-center justify-center p-2 rounded-md text-green-800 hover:text-gray-600 focus:outline-none">
className="inline-flex items-center justify-center p-2 rounded-md text-green-800 hover:text-gray-600 focus:outline-none"
>
<svg
className="h-6 w-6"
stroke="#15803D"
fill="#15803D"
viewBox="0 0 24 24">
viewBox="0 0 24 24"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
Expand Down
Loading