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;
70 changes: 70 additions & 0 deletions backend/controllers/userController.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
const mongoose = require("mongoose");
const User = require("../models/User");

exports.getUsers = async (_, res) => {
try {
const users = await User.find().select("-password -__v").lean();

return res.status(200).json({ users });
} catch (error) {
console.error("getUsers error:", error);
return res.status(500).json({ message: "Internal server error" });
}
};

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

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

const user = await User.findOne({ username: user_name })
.select("-password -__v")
.lean();

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

return res.status(200).json({ user });
} catch (error) {
console.error("getUserByUsername error:", error);
return res.status(500).json({ message: "Internal server error" });
}
};

exports.updateUser = async (req, res) => {
try {
const { id } = req.params;
if (!mongoose.Types.ObjectId.isValid(id)) {
return res.status(400).json({ message: "Invalid user id" });
}

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: "User not found" });

const updatedUser = await User.findByIdAndUpdate(
{ _id: id },
{ name, email, phone, gender, profile_picture },
{ new: true }
).select("-password -__v")
.lean();

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

return res.status(500).json({ message: "Failed to your profile" });
} catch (error) {
console.error("updateUser error:", error);
return res.status(500).json({ message: "Internal server error" });
}
};
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;
83 changes: 52 additions & 31 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 @@ -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