diff --git a/admin/src/App.jsx b/admin/src/App.jsx
index f0f90541..14625594 100644
--- a/admin/src/App.jsx
+++ b/admin/src/App.jsx
@@ -1,34 +1,88 @@
-import React from "react";
-import { Route, Routes } from "react-router-dom";
+import React, { useContext } from "react";
+import { Route, Routes, Navigate } from "react-router-dom";
+import { ToastContainer } from "react-toastify";
+
import Home from "./pages/Home";
import Add from "./pages/Add";
import List from "./pages/List";
import Order from "./pages/Order";
import Login from "./pages/Login";
-import { useContext } from "react";
+
import { adminDataContext } from "./Context/AdminProvider";
-import { ToastContainer } from "react-toastify";
+
+function ProtectedRoute({ children }) {
+ const { adminData } = useContext(adminDataContext);
+
+ if (!adminData) {
+ return ;
+ }
+
+ return children;
+}
function App() {
- let { adminData } = useContext(adminDataContext);
+ const { adminData } = useContext(adminDataContext);
+
return (
<>
- {!adminData ? (
-
- ) : (
- <>
-
- } />
- } />
- } />
- } />
- } />
-
- >
- )}
+
+
+ :
+ }
+ />
+
+
+
+
+ }
+ />
+
+
+
+
+ }
+ />
+
+
+
+
+ }
+ />
+
+
+
+
+ }
+ />
+
+
+ }
+ />
+
>
);
}
-export default App;
+export default App;
\ No newline at end of file
diff --git a/backend/config/Token.js b/backend/config/Token.js
index 0aa3e1e7..2fabc7d2 100644
--- a/backend/config/Token.js
+++ b/backend/config/Token.js
@@ -1,9 +1,16 @@
import jwt from "jsonwebtoken";
-export const genToken = (userId) => {
- return jwt.sign({ userId }, process.env.JWT_SECRET, {
- expiresIn: "7d",
- });
+export const genToken = (userId, role = "user") => {
+ return jwt.sign(
+ {
+ userId,
+ role,
+ },
+ process.env.JWT_SECRET,
+ {
+ expiresIn: "7d",
+ }
+ );
};
export const genToken1 = async (email) => {
@@ -15,4 +22,4 @@ export const genToken1 = async (email) => {
} catch (_error) {
console.log("token error");
}
-};
+};
\ No newline at end of file
diff --git a/backend/controller/authcontroller.js b/backend/controller/authcontroller.js
index b8e109c0..27646513 100644
--- a/backend/controller/authcontroller.js
+++ b/backend/controller/authcontroller.js
@@ -96,7 +96,7 @@ export const verifyOTP = async (req, res) => {
type: "new_user",
});
- const token = genToken(user._id);
+ const token = genToken(user._id, user.role);
res.cookie("token", token, {
httpOnly: true,
@@ -126,7 +126,7 @@ export const login = async (req, res) => {
const isMatch = await bcrypt.compare(password, user.password);
if (!isMatch) return res.status(400).json({ message: "Invalid credentials" });
- const token = genToken(user._id);
+ const token = genToken(user._id, user.role);
res.cookie("token", token, {
httpOnly: true,
secure: process.env.NODE_ENV === "production",
@@ -150,7 +150,7 @@ export const googleLogin = async (req, res) => {
user = await User.create({ name, email });
}
- const token = genToken(user._id);
+ const token = genToken(user._id, user.role);
res.cookie("token", token, {
httpOnly: true,
secure: process.env.NODE_ENV === "production",
diff --git a/backend/middleware/authorizeRoles.js b/backend/middleware/authorizeRoles.js
new file mode 100644
index 00000000..1ded95e8
--- /dev/null
+++ b/backend/middleware/authorizeRoles.js
@@ -0,0 +1,21 @@
+const authorizeRoles = (...allowedRoles) => {
+ return (req, res, next) => {
+ if (!req.role) {
+ return res.status(401).json({
+ success: false,
+ message: "Unauthorized",
+ });
+ }
+
+ if (!allowedRoles.includes(req.role)) {
+ return res.status(403).json({
+ success: false,
+ message: "Forbidden: Access denied",
+ });
+ }
+
+ next();
+ };
+};
+
+export default authorizeRoles;
\ No newline at end of file
diff --git a/backend/middleware/isAuth.js b/backend/middleware/isAuth.js
index 80a1d43e..9e5f1939 100644
--- a/backend/middleware/isAuth.js
+++ b/backend/middleware/isAuth.js
@@ -9,11 +9,14 @@ const isAuth = (req, res, next) => {
try {
const decoded = jwt.verify(token, process.env.JWT_SECRET);
+
req.userId = decoded.userId;
+ req.role = decoded.role;
+
next();
} catch (_error) {
return res.status(401).json({ message: "Invalid token" });
}
};
-export default isAuth;
+export default isAuth;
\ No newline at end of file
diff --git a/backend/model/userModel.js b/backend/model/userModel.js
index 372bea3e..6c9fccfa 100644
--- a/backend/model/userModel.js
+++ b/backend/model/userModel.js
@@ -24,13 +24,20 @@ const userSchema = new mongoose.Schema(
},
wishlist: [
- {
- type: mongoose.Schema.Types.ObjectId,
- ref: "Product",
- },
- ],
- resetPasswordToken: String,
- resetPasswordExpire: Date,
+ {
+ type: mongoose.Schema.Types.ObjectId,
+ ref: "Product",
+ },
+],
+
+role: {
+ type: String,
+ enum: ["user", "admin", "super_admin"],
+ default: "user",
+},
+
+resetPasswordToken: String,
+resetPasswordExpire: Date,
},
{
timestamps: true,
diff --git a/backend/routes/notificationRoutes.js b/backend/routes/notificationRoutes.js
index 20cf8bae..ceb6383e 100644
--- a/backend/routes/notificationRoutes.js
+++ b/backend/routes/notificationRoutes.js
@@ -55,36 +55,72 @@ router.put("/read-all-admin", adminAuth, adminRateLimiter, async (req, res) => {
});
// PUT /api/notifications/:id/read - Mark single notification as read
-router.put("/:id/read", async (req, res) => {
- try {
- const { id } = req.params;
- const notification = await Notification.findById(id);
- if (!notification) {
- return res.status(404).json({ message: "Notification not found" });
- }
+router.put(
+ "/:id/read",
+ isAuth,
+ userRateLimiter,
+ async (req, res) => {
+ try {
+ const { id } = req.params;
+ const notification = await Notification.findById(id);
+
+ if (!notification) {
+ return res.status(404).json({ message: "Notification not found" });
+ }
- notification.read = true;
- await notification.save();
- return res.status(200).json({ success: true, notification });
- } catch (error) {
- console.error("Error marking notification read:", error);
- return res.status(500).json({ message: "Server error" });
+ // Ownership check: prevent users from modifying other users' notifications
+ if (
+ !notification.isAdmin &&
+ notification.userId?.toString() !== req.userId
+ ) {
+ return res.status(403).json({
+ success: false,
+ message: "Forbidden: Access denied",
+ });
+ }
+
+ notification.read = true;
+ await notification.save();
+ return res.status(200).json({ success: true, notification });
+ } catch (error) {
+ console.error("Error marking notification read:", error);
+ return res.status(500).json({ message: "Server error" });
+ }
}
-});
+);
// DELETE /api/notifications/:id - Delete single notification
-router.delete("/:id", async (req, res) => {
- try {
- const { id } = req.params;
- const notification = await Notification.findByIdAndDelete(id);
- if (!notification) {
- return res.status(404).json({ message: "Notification not found" });
+router.delete(
+ "/:id",
+ isAuth,
+ userRateLimiter,
+ async (req, res) => {
+ try {
+ const { id } = req.params;
+ const notification = await Notification.findById(id);
+
+ if (!notification) {
+ return res.status(404).json({ message: "Notification not found" });
+ }
+
+ // Ownership check: prevent users from deleting other users' notifications
+ if (
+ !notification.isAdmin &&
+ notification.userId?.toString() !== req.userId
+ ) {
+ return res.status(403).json({
+ success: false,
+ message: "Forbidden: Access denied",
+ });
+ }
+
+ await notification.deleteOne();
+ return res.status(200).json({ success: true, message: "Notification deleted" });
+ } catch (error) {
+ console.error("Error deleting notification:", error);
+ return res.status(500).json({ message: "Server error" });
}
- return res.status(200).json({ success: true, message: "Notification deleted" });
- } catch (error) {
- console.error("Error deleting notification:", error);
- return res.status(500).json({ message: "Server error" });
}
-});
+);
-export default router;
+export default router;
\ No newline at end of file
diff --git a/backend/routes/productRoutes.js b/backend/routes/productRoutes.js
index 1703a133..5a429ec9 100644
--- a/backend/routes/productRoutes.js
+++ b/backend/routes/productRoutes.js
@@ -12,6 +12,8 @@ let productRoutes = express.Router();
productRoutes.post(
"/addproduct",
+ adminAuth,
+ adminRateLimiter,
upload.fields([
{ name: "image1", maxCount: 1 },
{ name: "image2", maxCount: 1 },
diff --git a/backend/routes/userRoutes.js b/backend/routes/userRoutes.js
index 7a2777bf..7febae8c 100644
--- a/backend/routes/userRoutes.js
+++ b/backend/routes/userRoutes.js
@@ -2,11 +2,27 @@ import express from "express";
import isAuth from "../middleware/isAuth.js";
import { getAdmin, getCurrentUser } from "../controller/userController.js";
import adminAuth from "../middleware/adminAuth.js";
-import { userRateLimiter, adminRateLimiter } from "../middleware/rateLimiters.js";
+import authorizeRoles from "../middleware/authorizeRoles.js";
+import {
+ userRateLimiter,
+ adminRateLimiter,
+} from "../middleware/rateLimiters.js";
let userRoutes = express.Router();
-userRoutes.get("/getCurrentUser", isAuth, userRateLimiter, getCurrentUser);
-userRoutes.get("/getadmin", adminAuth, adminRateLimiter, getAdmin);
+userRoutes.get(
+ "/getCurrentUser",
+ isAuth,
+ userRateLimiter,
+ getCurrentUser
+);
-export default userRoutes;
+userRoutes.get(
+ "/getadmin",
+ isAuth,
+ authorizeRoles("admin", "super_admin"),
+ adminRateLimiter,
+ getAdmin
+);
+
+export default userRoutes;
\ No newline at end of file