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