diff --git a/controllers/address.controller.js b/controllers/address.controller.js index c2e26e6..4e26f6d 100644 --- a/controllers/address.controller.js +++ b/controllers/address.controller.js @@ -59,7 +59,12 @@ const addAnAddress = asyncHandler(async (req, res) => { console.log(`Address added successfully to the user ${userId}`); - ApiResponse.success(res, "Address added successfully", newAddress, 201); + ApiResponse.success( + res, + "Address added successfully", + { address: newAddress, csrfToken: req.csrfToken() }, + 201 + ); } catch (err) { if (err.name === MONGOOSE_VALIDATION_ERROR) { return ApiResponse.validationError( @@ -93,13 +98,13 @@ const updateAnAddress = asyncHandler(async (req, res) => { const addressUser = await Address.findById(addressId).select("userId"); if (!addressUser) { - ApiResponse.notFound( + return ApiResponse.notFound( res, "Invalid address ID provided, Address not found" ); } if (addressUser.userId.toString() !== req.user._id.toString()) { - ApiResponse.forbidden( + return ApiResponse.forbidden( res, "Address doesn't belongs to loggedin user and hence can't update the address" ); @@ -115,13 +120,27 @@ const updateAnAddress = asyncHandler(async (req, res) => { await defaultAddress.save(); } } - const address = await Address.findByIdAndUpdate(addressId, req.body, { + + const validFields = Object.keys(Address.schema.paths); + const updateData = {}; + for (const key of Object.keys(req.body)) { + if (validFields.includes(key)) { + updateData[key] = req.body[key]; + } + } + const address = await Address.findByIdAndUpdate(addressId, updateData, { + //Don't update data from req.body directly it may lead to NoSQL Injection Attack new: true, runValidators: true, }); console.log("Address updated successfully"); - ApiResponse.success(res, "Address updated successfully", address, 200); + ApiResponse.success( + res, + "Address updated successfully", + { address, csrfToken: req.csrfToken() }, + 200 + ); } catch (err) { if (err.name === MONGOOSE_CAST_ERROR && err.kind === MONGOOSE_OBJECT_ID) { return ApiResponse.validationError( @@ -149,14 +168,14 @@ const deleteAnAddress = asyncHandler(async (req, res) => { const addressUser = await Address.findById(addressId).select("userId"); if (!addressUser) { - ApiResponse.notFound( + return ApiResponse.notFound( res, "Invalid address ID provided, Address not found" ); } if (addressUser.userId.toString() !== req.user._id.toString()) { - ApiResponse.forbidden( + return ApiResponse.forbidden( res, "Address doesn't belongs to loggedin user and hence can't delete the address" ); @@ -164,7 +183,12 @@ const deleteAnAddress = asyncHandler(async (req, res) => { const address = await Address.findByIdAndDelete(addressId); console.log("Address deleted successfully"); - ApiResponse.success(res, "Address deleted successfully", address, 200); + ApiResponse.success( + res, + "Address deleted successfully", + { csrfToken: req.csrfToken() }, + 200 + ); } catch (err) { if (err.name === MONGOOSE_CAST_ERROR && err.kind === MONGOOSE_OBJECT_ID) { return ApiResponse.validationError( diff --git a/controllers/attribute.controller.js b/controllers/attribute.controller.js index b2e9fda..d1189d9 100644 --- a/controllers/attribute.controller.js +++ b/controllers/attribute.controller.js @@ -8,7 +8,6 @@ const { MONGOOSE_CAST_ERROR, MONGOOSE_OBJECT_ID, } = require("../constants/models.constants"); -const { get } = require("mongoose"); const createAttribute = asyncHandler(async (req, res) => { try { @@ -16,7 +15,10 @@ const createAttribute = asyncHandler(async (req, res) => { values = values.split(","); const attribute = await Attribute.create({ ...req.body, values }); - ApiResponse.success(res, "Attribute created successfully", attribute); + ApiResponse.success(res, "Attribute created successfully", { + attribute, + csrfToken: req.csrfToken(), + }); } catch (error) { if (error.name === MONGOOSE_VALIDATION_ERROR) { return ApiResponse.validationError( @@ -74,6 +76,19 @@ const updateAnAttribute = asyncHandler(async (req, res) => { try { const { name, values } = req.body; + // Protection against NoSQL injection attack + if (name) { + if (typeof name !== "string") { + return ApiResponse.validationError(res, "Invalid input format 1"); + } + } + const newValues = values.split(","); + if (newValues) { + if (!Array.isArray(newValues)) { + return ApiResponse.validationError(res, "Invalid input format 2"); + } + } + const attribute = await Attribute.findById(req.params.id); if (!attribute) { @@ -84,10 +99,12 @@ const updateAnAttribute = asyncHandler(async (req, res) => { return ApiResponse.conflict(res, "Attribute already exists", 400); } const existingValues = attribute.values; - const newValues = values.split(","); const updatedAttribute = await Attribute.findByIdAndUpdate( attribute._id, - { name, values: [...new Set([...existingValues, ...newValues])] }, + { + name: typeof name === "string" ? name : attribute.name, // name should only be string to protect against NoSQL Injection attack + values: [...new Set([...existingValues, ...newValues])], + }, { new: true, runValidators: true, @@ -97,7 +114,11 @@ const updateAnAttribute = asyncHandler(async (req, res) => { ApiResponse.success( res, "Attribute updated successfully", - updatedAttribute + { + attribute: updatedAttribute, + csrfToken: req.csrfToken(), + }, + 200 ); } catch (error) { if (error.name === MONGOOSE_VALIDATION_ERROR) { @@ -134,7 +155,9 @@ const deleteAnAttribute = asyncHandler(async (req, res) => { return ApiResponse.notFound(res, "Attribute not found", 404); } await Attribute.findByIdAndDelete(id); - ApiResponse.success(res, "Attribute deleted successfully"); + ApiResponse.success(res, "Attribute deleted successfully", { + csrfToken: req.csrfToken(), + }); } catch (error) { if ( error.name === MONGOOSE_CAST_ERROR && diff --git a/controllers/cart.controller.js b/controllers/cart.controller.js index a6fe876..489b251 100644 --- a/controllers/cart.controller.js +++ b/controllers/cart.controller.js @@ -90,7 +90,12 @@ const createACart = asyncHandler(async (req, res) => { } const cart = await Cart.create({ customerId: userId, cartItems }); - ApiResponse.success(res, "Cart created successfully", cart, 200); + ApiResponse.success( + res, + "Cart created successfully", + { cart, csrfTolken: req.csrfToken() }, + 200 + ); } catch (error) { if ( error.name === MONGOOSE_CAST_ERROR && @@ -144,7 +149,12 @@ const addItemsToCart = asyncHandler(async (req, res) => { { $set: { cartItems: mergeCartItems(cart.cartItems, cartItems) } }, { new: true, runValidators: true } ); - ApiResponse.success(res, "Cart updated successfully", updatedCart, 200); + ApiResponse.success( + res, + "Cart updated successfully", + { cart: updatedCart, csrfTolken: req.csrfToken() }, + 200 + ); } catch (error) { ApiResponse.error(res, "Error while updating cart", 500, error); } diff --git a/controllers/category.controller.js b/controllers/category.controller.js index 6cd3444..c140688 100644 --- a/controllers/category.controller.js +++ b/controllers/category.controller.js @@ -8,16 +8,21 @@ const Category = require("../model/category.model"); const Product = require("../model/product.model"); const ApiResponse = require("../utils/ApiResponse"); const asyncHandler = require("../utils/asyncHandler"); +const invalidFieldMessage = require("../utils/invalidFieldMessage"); const createCategory = asyncHandler(async (req, res) => { try { let thumbnail; - console.log(req.file); if (req.file) { thumbnail = req.file.filename; } const category = await Category.create({ ...req.body, thumbnail }); - ApiResponse.success(res, "Category created successfully", category, 201); + ApiResponse.success( + res, + "Category created successfully", + { category, csrfToken: req.csrfToken() }, + 201 + ); } catch (error) { if (error.name === MONGOOSE_VALIDATION_ERROR) { return ApiResponse.error(res, invalidFieldMessage(error), 400); @@ -46,17 +51,14 @@ const getCategories = asyncHandler(async (req, res) => { const deleteCategory = asyncHandler(async (req, res) => { try { - console.log(req.params); const category = await Category.findOne(req.params); if (!category) { return ApiResponse.notFound(res, "Category not found", 404); } - // Use async/await with Promise.all to handle product updates const products = await Product.find({ categories: category._id }); - // Remove the category reference from each product await Promise.all( products.map(async (product) => { product.category = product.category.filter((c) => c !== category._id); @@ -67,7 +69,12 @@ const deleteCategory = asyncHandler(async (req, res) => { // Delete the category await category.deleteOne(); - ApiResponse.success(res, "Category deleted successfully", {}); + ApiResponse.success( + res, + "Category deleted successfully", + { csrfToken: req.csrfToken() }, + 200 + ); } catch (error) { if ( error.name === MONGOOSE_CAST_ERROR && @@ -82,13 +89,27 @@ const deleteCategory = asyncHandler(async (req, res) => { const updateCategory = asyncHandler(async (req, res) => { try { const { name, slug, isActive } = req.body; + + const isProductActive = + isActive === "true" ? true : isActive === "false" ? false : isActive; + if ( + typeof name !== "string" || + typeof slug !== "string" || + typeof isProductActive !== "boolean" + ) { + return ApiResponse.validationError(res, "Invalid input data", {}, 400); + } let thumbnail; if (req.file) { thumbnail = req.file.filename; } - const category = await Category.find(req.params, { - new: true, - }); + const category = await Category.findOne({ slug: req.params.slug }).select( + "_id" + ); + + if (!category) { + return ApiResponse.notFound(res, "Category not found", 404); + } if (name) { // Can we use pre save hooks to save update products category name instead of doing this here. @@ -102,8 +123,8 @@ const updateCategory = asyncHandler(async (req, res) => { }); } - const updatedCategory = await Category.findOneAndUpdate( - req.params, + const updatedCategory = await Category.findByIdAndUpdate( + category._id, { name, slug, @@ -113,7 +134,12 @@ const updateCategory = asyncHandler(async (req, res) => { { new: true, runValidators: true } ); - ApiResponse.success(res, "Category updated successfully", updatedCategory); + ApiResponse.success( + res, + "Category updated successfully", + { category: updatedCategory, csrfToken: req.csrfToken() }, + 200 + ); } catch (error) { ApiResponse.error(res, "Error while updating category", 500); } diff --git a/controllers/product.controller.js b/controllers/product.controller.js index ff892ef..bb99877 100644 --- a/controllers/product.controller.js +++ b/controllers/product.controller.js @@ -77,7 +77,8 @@ const addAProduct = asyncHandler(async (req, res) => { ApiResponse.success( res, "Product created successfully, Now add variations and prices for it", - product + { product, csrfToken: req.csrfToken() }, + 201 ); } catch (error) { if (error.name === MONGOOSE_VALIDATION_ERROR) { @@ -177,7 +178,10 @@ const updateAProduct = asyncHandler(async (req, res) => { { new: true, runValidators: true } ); - ApiResponse.success(res, "Product updated successfully", updatedProduct); + ApiResponse.success(res, "Product updated successfully", { + product: updatedProduct, + csrfToken: req.csrfToken(), + }); } catch (error) { if ( error.name === MONGOOSE_CAST_ERROR && @@ -201,9 +205,18 @@ const updateProductThumbnail = asyncHandler(async (req, res) => { try { const { id } = req.params; - const thumbnail = req.file.filenane; + const thumbnail = req.file.filename; - const product = Product.findByIdAndUpdate( + if (typeof thumbnail !== "string") { + return ApiResponse.validationError( + res, + "Invalid input data of thumbnail", + {}, + 400 + ); + } + + const product = await Product.findByIdAndUpdate( id, { thumbnail }, { new: true, runValidators: true } @@ -213,13 +226,18 @@ const updateProductThumbnail = asyncHandler(async (req, res) => { return ApiResponse.notFound(res, "Product not found"); } - ApiResponse.success(res, "Thumbnail Updated SUccessfully", product, 201); + ApiResponse.success( + res, + "Thumbnail Updated Successfully", + { product, csrfToken: req.csrfToken() }, + 201 + ); } catch (error) { if ( error.name === MONGOOSE_CAST_ERROR && error.kind === MONGOOSE_OBJECT_ID ) { - ApiResponse.validationError( + return ApiResponse.validationError( res, "Thummbnail Update Failed", { id: "Invalid productId provided" }, @@ -234,9 +252,9 @@ const updateProductGallery = asyncHandler(async (req, res) => { try { const { id } = req.params; - const gallery = req.files.gallery.map((file) => file.filename); + const gallery = req.files.map((file) => file.filename); - const product = Product.findByIdAndUpdate( + const product = await Product.findByIdAndUpdate( id, { gallery }, { new: true, runValidators: true } @@ -246,13 +264,18 @@ const updateProductGallery = asyncHandler(async (req, res) => { return ApiResponse.notFound(res, "Product not found"); } - ApiResponse.success(res, "Gallery. Updated SUccessfully", product, 201); + ApiResponse.success( + res, + "Gallery. Updated SUccessfully", + { product, csrfToken: req.csrfToken() }, + 201 + ); } catch (error) { if ( error.name === MONGOOSE_CAST_ERROR && error.kind === MONGOOSE_OBJECT_ID ) { - ApiResponse.validationError( + return ApiResponse.validationError( res, "Gallery Update Failed", { id: "Invalid productId provided" }, @@ -270,16 +293,21 @@ const deleteAProduct = asyncHandler(async (req, res) => { const product = await Product.findByIdAndDelete(id); if (!product) { - ApiResponse.notFound(res, "Product not found"); + return ApiResponse.notFound(res, "Product not found"); } - ApiResponse.success(res, "Product deleted successfully", {}, 201); + ApiResponse.success( + res, + "Product deleted successfully", + { csrfToken: req.csrfToken() }, + 201 + ); } catch (error) { if ( error.name === MONGOOSE_CAST_ERROR && error.kind === MONGOOSE_OBJECT_ID ) { - ApiResponse.validationError( + return ApiResponse.validationError( res, "Product deletion failed", { id: "Invalid productId provided" }, diff --git a/controllers/roles.controller.js b/controllers/roles.controller.js index b858790..69c8ff2 100644 --- a/controllers/roles.controller.js +++ b/controllers/roles.controller.js @@ -10,11 +10,11 @@ const assignRoleToUser = asyncHandler(async (req, res) => { let user = await User.findById(req.params.id).select("roles name"); if (!user) { - ApiResponse.notFound(res, "User not found"); + return ApiResponse.notFound(res, "User not found"); } if (!req.body.roles) { - ApiResponse.validationError( + return ApiResponse.validationError( res, "Roles field is empty", { roles: "Roles field is required" }, @@ -34,7 +34,7 @@ const assignRoleToUser = asyncHandler(async (req, res) => { validRoles.includes(role) ); if (!areAllRolesValid) { - ApiResponse.validationError( + return ApiResponse.validationError( res, "Roles contains some invalid roles", { roles: "All roles values must be valid" }, @@ -51,7 +51,10 @@ const assignRoleToUser = asyncHandler(async (req, res) => { ApiResponse.success( res, "User updated successfully", - rolesObjectToArray(JSON.parse(JSON.stringify(user))), + { + user: rolesObjectToArray(JSON.parse(JSON.stringify(user))), + csrfToken: req.csrfToken(), + }, 200 ); }); @@ -60,11 +63,11 @@ const revokeRoleFromUser = asyncHandler(async (req, res) => { let user = await User.findById(req.params.id).select("roles name"); if (!user) { - ApiResponse.notFound(res, "User not found"); + return ApiResponse.notFound(res, "User not found"); } if (!req.body.roles) { - ApiResponse.validationError( + return ApiResponse.validationError( res, "Roles field is empty", { roles: "Roles field is required" }, @@ -83,7 +86,7 @@ const revokeRoleFromUser = asyncHandler(async (req, res) => { validRoles.includes(role) ); if (!areAllRolesValid) { - ApiResponse.validationError( + return ApiResponse.validationError( res, "Roles contains some invalid roles", { roles: "All roles values must be valid" }, @@ -92,7 +95,7 @@ const revokeRoleFromUser = asyncHandler(async (req, res) => { } if (rolesToRevoke.includes(CUSTOMER)) { - ApiResponse.forbidden( + return ApiResponse.forbidden( res, "Revoke request contains default user role, can't remove this. If you want user to not access this platform kindly change the status of user to Blocked." ); @@ -111,7 +114,10 @@ const revokeRoleFromUser = asyncHandler(async (req, res) => { ApiResponse.success( res, "User updated successfully", - rolesObjectToArray(JSON.parse(JSON.stringify(user))), + { + user: rolesObjectToArray(JSON.parse(JSON.stringify(user))), + csrfToken: req.csrfToken(), + }, 200 ); }); diff --git a/controllers/tag.controller.js b/controllers/tag.controller.js index 59da51d..f9454d3 100644 --- a/controllers/tag.controller.js +++ b/controllers/tag.controller.js @@ -13,7 +13,10 @@ const createTag = async (req, res) => { const tag = await Tag.create({ name }); - ApiResponse.success(res, "Tag created successfully", tag); + ApiResponse.success(res, "Tag created successfully", { + tag, + csrfToken: req.csrfToken(), + }); } catch (error) { if (error.code === MONGOOSE_DUPLICATE_KEY) { return ApiResponse.conflict(res, "Tag already exists", 409); @@ -69,7 +72,10 @@ const updateTag = async (req, res) => { tag.name = name || tag.name; await tag.save(); - ApiResponse.success(res, "Tag updated successfully", tag); + ApiResponse.success(res, "Tag updated successfully", { + tag, + csrfToken: req.csrfToken(), + }); } catch (error) { if (err.name === MONGOOSE_CAST_ERROR && err.kind === MONGOOSE_OBJECT_ID) { return ApiResponse.validationError(res, "Invalid tag Fromat provided"); @@ -90,7 +96,9 @@ const deleteTag = async (req, res) => { await tag.deleteOne(); - ApiResponse.success(res, "Tag deleted successfully", {}); + ApiResponse.success(res, "Tag deleted successfully", { + csrfToken: req.csrfToken(), + }); } catch (error) { ApiResponse.error(res, "Error while deleting tag", error, 500); } diff --git a/controllers/user.controller.js b/controllers/user.controller.js index 0ca8f43..e14f8f4 100644 --- a/controllers/user.controller.js +++ b/controllers/user.controller.js @@ -11,6 +11,8 @@ const { MONGOOSE_DUPLICATE_KEY, } = require("../constants/models.constants"); const invalidFieldMessage = require("../utils/invalidFieldMessage"); +const { validateUsername } = require("../utils/inputValidation/validators"); +const UPLOAD_ROOT = path.resolve(__dirname, "..", "public", "uploads"); const generateAccessAndRefreshToken = async (userId) => { try { @@ -59,7 +61,7 @@ const registerUser = asyncHandler(async (req, res) => { ApiResponse.success( res, "User added successfully", - rolesObjectToArray(createdUser), + { user: rolesObjectToArray(createdUser), csrfToken: req.csrfToken() }, 201 ); } catch (err) { @@ -84,7 +86,6 @@ const registerUser = asyncHandler(async (req, res) => { }); const loginUser = asyncHandler(async (req, res) => { - // Get data vaidated data from frontend let { usernameOrEmail, password } = req.body; // Find user @@ -122,17 +123,22 @@ const loginUser = asyncHandler(async (req, res) => { return ApiResponse.success( res, + "User logged In Successfully", { user: rolesObjectToArray(loggedInUser), accessToken, refreshToken, + csrfToken: req.csrfToken(), }, - "User logged In Successfully", 200 ); }); const logout = asyncHandler(async (req, res) => { + if (!req.user?._id) { + ApiResponse.notFound(res, "User not loggedin"); + } + // Remove remove user credentials from Db await User.findByIdAndUpdate( req.user._id, @@ -156,7 +162,12 @@ const logout = asyncHandler(async (req, res) => { //Send response back - ApiResponse.success(res, "User logged out successfully", {}, 200); + ApiResponse.success( + res, + "User logged out successfully", + { csrfToken: req.csrfToken() }, + 200 + ); }); const refreshAccessToken = asyncHandler(async (req, res) => { @@ -199,11 +210,12 @@ const refreshAccessToken = asyncHandler(async (req, res) => { ApiResponse.success( res, + "New Tokens generated succesfully", { accessToken, refreshToken: newRefreshToken, + csrfToken: req.csrfToken(), }, - "New Tokens generated succesfully", 200 ); } catch (error) { @@ -236,8 +248,8 @@ const updateUserInfo = asyncHandler(async (req, res) => { console.log("User updated successfully"); ApiResponse.success( res, - rolesObjectToArray(updatedUser), "User updated successfully", + { user: rolesObjectToArray(updatedUser), csrfToken: req.csrfToken() }, 200 ); } catch (err) { @@ -268,6 +280,16 @@ const updateAvatar = asyncHandler(async (req, res) => { ApiResponse.notFound(res, "User not loggedin or found"); } + const validUsername = validateUsername(user.username); + if (!validUsername) { + ApiResponse.validationError( + res, + "This should not happen. Avatar update failed.", + { username: "Invalid username taken from cookies" }, + 400 + ); + } + if (!req.file) { ApiResponse.validationError( res, @@ -280,14 +302,7 @@ const updateAvatar = asyncHandler(async (req, res) => { //Find old avatar from DB let oldAvatarPath = ""; if (user.avatar) { - oldAvatarPath = path.join( - __dirname, - "..", - "public", - "uploads", - user.username, - user.avatar - ); + oldAvatarPath = path.resolve(UPLOAD_ROOT, user.avatar); } // Save new Avatar to DB @@ -302,7 +317,12 @@ const updateAvatar = asyncHandler(async (req, res) => { } }); } - ApiResponse.success(res, user, "User avatar updated successfully", 200); + ApiResponse.success( + res, + "User avatar updated successfully", + { user: rolesObjectToArray(user), csrfToken: req.csrfToken() }, + 200 + ); }); const updateUsername = asyncHandler(async (req, res) => { @@ -332,31 +352,11 @@ const updateUsername = asyncHandler(async (req, res) => { runValidators: true, // Run validation on update } ).lean(); - console.log("User updated successfully"); - - // Rename folder for user according to the new username - const oldPath = path.join(__dirname, "..", "public", "uploads", oldUsername); - console.log(oldPath); - const newPath = path.join( - __dirname, - "..", - "public", - "uploads", - incomingUsername - ); - - fs.rename(oldPath, newPath, (err) => { - if (err) { - console.log(err); - } else { - console.log("Successfully renamed the directory."); - } - }); ApiResponse.success( res, "User updated successfully", - rolesObjectToArray(user), + { user: rolesObjectToArray(user), csrfToken: req.csrfToken() }, 200 ); }); @@ -400,5 +400,3 @@ module.exports = { getAllUsers, getUsersByRole, }; - -// Is it necessary to check for allowed updates while updating user? diff --git a/controllers/variation.controller.js b/controllers/variation.controller.js index 2e03a69..d8d45ab 100644 --- a/controllers/variation.controller.js +++ b/controllers/variation.controller.js @@ -89,12 +89,11 @@ const validateAttributes = (productAttributes, variationAttributes) => { }; const createVariation = async (res, variationData) => { - console.log("creating variation"); const variation = await Variation.create(variationData); return ApiResponse.success( res, "Variation added successfully", - variation, + { variation, csrfToken: req.csrfToken() }, 201 ); }; @@ -209,6 +208,10 @@ const updateAVariation = asyncHandler(async (req, res) => { const variation = await Variation.findById(id).select("productId"); + if (!variation) { + return ApiResponse.notFound(res, "Variation doesnot exist", 404); + } + const product = await Product.findById(variation.productId); if (!product) { @@ -248,7 +251,7 @@ const updateAVariation = asyncHandler(async (req, res) => { return ApiResponse.success( res, "Variation updated successfully", - updatedVariation, + { variation: updatedVariation, csrfToken: req.csrfToken() }, 200 ); } @@ -290,7 +293,7 @@ const updateAVariation = asyncHandler(async (req, res) => { return ApiResponse.success( res, "Variation updated successfully", - updatedVariation, + { variation: updatedVariation, csrfToken: req.csrfToken() }, 200 ); } @@ -335,7 +338,12 @@ const deleteAVariation = asyncHandler(async (req, res) => { if (!deletedVariation) { return ApiResponse.notFound(res, "Variation doesnot exist", 404); } - return ApiResponse.success(res, "Variation deleted successfully", {}, 200); + return ApiResponse.success( + res, + "Variation deleted successfully", + { csrfToken: req.csrfToken() }, + 200 + ); } catch (error) { if ( error.name === MONGOOSE_CAST_ERROR && @@ -361,7 +369,7 @@ const deleteAllVariationsOfAProduct = asyncHandler(async (req, res) => { return ApiResponse.success( res, "Variations deleted successfully", - deletedVariations, + { csrfToken: req.csrfToken() }, 200 ); } catch (error) { diff --git a/index.js b/index.js index 8b68930..9894ddd 100644 --- a/index.js +++ b/index.js @@ -2,15 +2,17 @@ require("dotenv").config(); const express = require("express"); const cors = require("cors"); const path = require("path"); +const helmet = require("helmet"); +const mongoose = require("mongoose"); +const cookieParser = require("cookie-parser"); const { logger } = require("./middleware/logEvents.middleware"); // const errorHandler = require('./middleware/errorHandler') const corsOptions = require("./config/corsOptions"); -const mongoose = require("mongoose"); -// const session = require("express-session"); -// const csrf = require("lusca").csrf; -const cookieParser = require("cookie-parser"); const connectDB = require("./config/dbConn"); const restrictDirectoryAccess = require("./middleware/uploads.middleware"); +const expressSession = require("./middleware/espressSession.middleware"); +const csrfProtection = require("./middleware/csrf.middleware"); + const PORT = process.env.PORT || 3000; const app = express(); @@ -23,6 +25,8 @@ app.use(logger); //Cross Origin Resource Sharing app.use(cors(corsOptions)); +app.use(helmet()); // Add security headers to HTTP responses + // Middleware to parse x-www-form-urlencoded data app.use(express.urlencoded({ extended: true, limit: process.env.REQ_LIMIT })); @@ -32,6 +36,12 @@ app.use(express.json()); //middleware for cookies app.use(cookieParser()); +// Apply session middleware +app.use(expressSession); + +// Apply CSRF middleware (after express session) +app.use(csrfProtection); + // Middleware to restrict access to the directory itself app.use("/api/v1/uploads", restrictDirectoryAccess); @@ -42,6 +52,10 @@ app.use( express.static(path.join(__dirname, "public/uploads")) ); +app.get("/api/v1/csrf-token", csrfProtection, (req, res) => { + res.status(200).json({ csrfToken: req.csrfToken() }); +}); + //routes app.use("/api/v1", require("./routes/root")); app.use("/api/v1/user", require("./routes/user.route")); @@ -50,7 +64,7 @@ app.use("/api/v1/address", require("./routes/address.route")); app.use("/api/v1/category", require("./routes/category.route")); app.use("/api/v1/tag", require("./routes/tag.route")); app.use("/api/v1/attribute", require("./routes/attribute.route")); -app.use("/api/v1/product", require("./routes/product.routes")); +app.use("/api/v1/product", require("./routes/product.route")); app.use("/api/v1/variation", require("./routes/variation.route")); app.use("/api/v1/cart", require("./routes/cart.route")); diff --git a/middleware/csrf.middleware.js b/middleware/csrf.middleware.js new file mode 100644 index 0000000..5bb391a --- /dev/null +++ b/middleware/csrf.middleware.js @@ -0,0 +1,11 @@ +const csrf = require("csurf"); + +const csrfProtection = csrf({ + cookie: { + httpOnly: true, // Prevent JavaScript access to the CSRF cookie + secure: process.env.NODE_ENV === "production", // Only send the cookie over HTTPS in production + sameSite: "Strict", // Prevent cookies from being sent in cross-site requests + }, +}); + +module.exports = csrfProtection; diff --git a/middleware/espressSession.middleware.js b/middleware/espressSession.middleware.js new file mode 100644 index 0000000..e43b574 --- /dev/null +++ b/middleware/espressSession.middleware.js @@ -0,0 +1,16 @@ +const session = require("express-session"); + +const expressSession = session({ + secret: process.env.EXPRESS_SESSION_SECRET, + resave: false, // Do not save the session if it hasn't been modified + saveUninitialized: true, // Save uninitialized sessions for login tracking + cookie: { + httpOnly: true, + secure: process.env.NODE_ENV === "production", + maxAge: 1000 * 60 * 60, // Time after which cookie expires and refreshes + sameSite: "Strict", // Prevent cookies from being sent in cross-site requests + // rolling: true, // Reset the session maxAge on every request + }, +}); + +module.exports = expressSession; diff --git a/middleware/rateLimit.middleware.js b/middleware/rateLimit.middleware.js new file mode 100644 index 0000000..71d21b8 --- /dev/null +++ b/middleware/rateLimit.middleware.js @@ -0,0 +1,43 @@ +const rateLimit = require("express-rate-limit"); +const ApiResponse = require("../utils/ApiResponse"); + +const limiter = (time, maxRequest) => { + const timeinMs = (time) => { + if (time.endsWith("s")) { + return time.replace("s", "") * 1000; + } else if (time.endsWith("m")) { + return time.replace("m", "") * 60 * 1000; + } else if (time.endsWith("h")) { + return time.replace("h", "") * 60 * 60 * 1000; + } else if (time.endsWith("d")) { + return time.replace("d", "") * 24 * 60 * 60 * 1000; + } else { + return null; + } + }; + + if (!timeinMs(time)) { + throw new Error("Invalid time format"); + } + + if (typeof maxRequest !== "number") { + throw new Error("maxRequest must be a number"); + } + + return rateLimit({ + windowMs: timeinMs(time), // Convert time to milliseconds + max: maxRequest, // Maximum requests per window + standardHeaders: true, // Include rate limit info in standard headers + legacyHeaders: false, // Disable legacy headers + keyGenerator: (req) => req.ip, // Use the request IP as the unique key + handler: (req, res) => { + const userIp = req.ip; + ApiResponse.tooManyRequests( + res, + `Too many requests from IP: ${userIp}. Please try again later.` + ); + }, + }); +}; + +module.exports = limiter; diff --git a/middleware/verifyJWT.middleware.js b/middleware/verifyJWT.middleware.js index 2cad785..d2c84d7 100644 --- a/middleware/verifyJWT.middleware.js +++ b/middleware/verifyJWT.middleware.js @@ -1,5 +1,5 @@ const User = require("../model/user.model"); -const apiXRes = require("../utils/ApiResponse"); +const ApiResponse = require("../utils/ApiResponse"); const asyncHandler = require("../utils/asyncHandler"); const jwt = require("jsonwebtoken"); @@ -10,7 +10,7 @@ const verifyJWT = asyncHandler(async (req, res, next) => { req.header("Authorisation")?.replace("Bearer ", ""); // req.header for requests which came from mobile application if (!token) { - apiXRes.unauthorized( + return ApiResponse.unauthorized( res, "Unauthorised request make sure you are logged in and you have proper access rights" ); @@ -23,14 +23,14 @@ const verifyJWT = asyncHandler(async (req, res, next) => { ); if (!user) { - apiXRes.notFound(res, "Invalid access token"); + return ApiResponse.notFound(res, "Invalid access token"); } req.user = user; next(); } catch (error) { - apiXRes.unauthorized(res, error?.message || "Invalid access token"); + ApiResponse.unauthorized(res, error?.message || "Invalid access token"); } }); diff --git a/model/user.model.js b/model/user.model.js index 3d5f6f8..f17e1d8 100644 --- a/model/user.model.js +++ b/model/user.model.js @@ -11,7 +11,13 @@ const { } = require("../constants/models.constants"); const bcrypt = require("bcrypt"); const jwt = require("jsonwebtoken"); -const { emailRegex, phoneRegex, usernameRegex, passwordRegex, nameRegex} = require("../constants/regex.constants"); +const { + emailRegex, + phoneRegex, + usernameRegex, + passwordRegex, + nameRegex, +} = require("../constants/regex.constants"); const ROLES_LIST = require("../config/rolesList"); const userSchema = new mongoose.Schema( @@ -36,7 +42,7 @@ const userSchema = new mongoose.Schema( name: { type: String, trim: true, - match : [nameRegex, "Name not in proper format"], + match: [nameRegex, "Name not in proper format"], required: [true, "Name is required"], }, avatar: String, //Save Images to a seperate folder and store the link of the image to database @@ -79,7 +85,7 @@ const userSchema = new mongoose.Schema( { timestamps: true } ); -userSchema.pre("validate", function(next){ +userSchema.pre("validate", function (next) { if (this.isModified("password") && !passwordRegex.test(this.password)) { this.invalidate( "password", @@ -87,7 +93,7 @@ userSchema.pre("validate", function(next){ ); } next(); -}) +}); userSchema.pre("save", async function (next) { if (this.isModified("password")) { @@ -130,11 +136,3 @@ userSchema.methods.rolesObjectToArray = function () { const User = mongoose.model(USER, userSchema); module.exports = User; - - -/* - put regex in the model. - remove validators from the code. - validate directly while saving in the DB. - -*/ diff --git a/package-lock.json b/package-lock.json index cb760d7..bbd1d4c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,14 +12,16 @@ "bcrypt": "^5.1.1", "cookie-parser": "^1.4.7", "cors": "^2.8.5", + "csurf": "^1.10.0", "date-fns": "^3.6.0", "dotenv": "^16.4.5", "express": "^4.21.1", - "fs": "^0.0.1-security", + "express-rate-limit": "^7.4.1", + "express-session": "^1.18.1", + "helmet": "^8.0.0", "jsonwebtoken": "^9.0.2", "mongoose": "^8.6.0", "multer": "^1.4.5-lts.1", - "path": "^0.12.7", "slugify": "^1.6.6", "uuid": "^10.0.0" } @@ -199,6 +201,7 @@ "version": "1.20.3", "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz", "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==", + "license": "MIT", "dependencies": { "bytes": "3.1.2", "content-type": "~1.0.5", @@ -264,6 +267,7 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "license": "MIT", "engines": { "node": ">= 0.8" } @@ -272,6 +276,7 @@ "version": "1.0.7", "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==", + "license": "MIT", "dependencies": { "es-define-property": "^1.0.0", "es-errors": "^1.3.0", @@ -346,6 +351,7 @@ "version": "1.0.5", "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "license": "MIT", "engines": { "node": ">= 0.6" } @@ -354,6 +360,7 @@ "version": "0.7.1", "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz", "integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==", + "license": "MIT", "engines": { "node": ">= 0.6" } @@ -401,6 +408,93 @@ "node": ">= 0.10" } }, + "node_modules/csrf": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/csrf/-/csrf-3.1.0.tgz", + "integrity": "sha512-uTqEnCvWRk042asU6JtapDTcJeeailFy4ydOQS28bj1hcLnYRiqi8SsD2jS412AY1I/4qdOwWZun774iqywf9w==", + "license": "MIT", + "dependencies": { + "rndm": "1.2.0", + "tsscmp": "1.0.6", + "uid-safe": "2.1.5" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/csurf": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/csurf/-/csurf-1.10.0.tgz", + "integrity": "sha512-fh725p0R83wA5JukCik5hdEko/LizW/Vl7pkKDa1WJUVCosg141mqaAWCScB+nkEaRMFMGbutHMOr6oBNc/j9A==", + "license": "MIT", + "dependencies": { + "cookie": "0.3.1", + "cookie-signature": "1.0.6", + "csrf": "3.1.0", + "http-errors": "~1.7.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/csurf/node_modules/cookie": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.3.1.tgz", + "integrity": "sha512-+IJOX0OqlHCszo2mBUq+SrEbCj6w7Kpffqx60zYbPTFaO4+yYgRjHwcZNpWvaTylDHaV7PPmBHzSecZiMhtPgw==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/csurf/node_modules/depd": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/csurf/node_modules/http-errors": { + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.3.tgz", + "integrity": "sha512-ZTTX0MWrsQ2ZAhA1cejAwDLycFsd7I7nVtnkT3Ol0aqodaKW+0CTZDQ1uBv5whptCnc8e8HeRRJxRs0kmm/Qfw==", + "license": "MIT", + "dependencies": { + "depd": "~1.1.2", + "inherits": "2.0.4", + "setprototypeof": "1.1.1", + "statuses": ">= 1.5.0 < 2", + "toidentifier": "1.0.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/csurf/node_modules/setprototypeof": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz", + "integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==", + "license": "ISC" + }, + "node_modules/csurf/node_modules/statuses": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", + "integrity": "sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/csurf/node_modules/toidentifier": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz", + "integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==", + "license": "MIT", + "engines": { + "node": ">=0.6" + } + }, "node_modules/date-fns": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-3.6.0.tgz", @@ -422,6 +516,7 @@ "version": "1.1.4", "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "license": "MIT", "dependencies": { "es-define-property": "^1.0.0", "es-errors": "^1.3.0", @@ -452,6 +547,7 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "license": "MIT", "engines": { "node": ">= 0.8", "npm": "1.2.8000 || >= 1.4.16" @@ -489,7 +585,8 @@ "node_modules/ee-first": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", - "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", + "license": "MIT" }, "node_modules/emoji-regex": { "version": "8.0.0", @@ -501,6 +598,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "license": "MIT", "engines": { "node": ">= 0.8" } @@ -509,6 +607,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz", "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==", + "license": "MIT", "dependencies": { "get-intrinsic": "^1.2.4" }, @@ -520,6 +619,7 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", "engines": { "node": ">= 0.4" } @@ -527,12 +627,14 @@ "node_modules/escape-html": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", - "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==" + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "license": "MIT" }, "node_modules/etag": { "version": "1.8.1", "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "license": "MIT", "engines": { "node": ">= 0.6" } @@ -541,6 +643,7 @@ "version": "4.21.1", "resolved": "https://registry.npmjs.org/express/-/express-4.21.1.tgz", "integrity": "sha512-YSFlK1Ee0/GC8QaO91tHcDxJiE/X4FbpAyQWkxAvG6AXCuR65YzK8ua6D9hvi/TzUfZMpc+BwuM1IPw8fmQBiQ==", + "license": "MIT", "dependencies": { "accepts": "~1.3.8", "array-flatten": "1.1.1", @@ -578,10 +681,60 @@ "node": ">= 0.10.0" } }, + "node_modules/express-rate-limit": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-7.4.1.tgz", + "integrity": "sha512-KS3efpnpIDVIXopMc65EMbWbUht7qvTCdtCR2dD/IZmi9MIkopYESwyRqLgv8Pfu589+KqDqOdzJWW7AHoACeg==", + "license": "MIT", + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://github.com/sponsors/express-rate-limit" + }, + "peerDependencies": { + "express": "4 || 5 || ^5.0.0-beta.1" + } + }, + "node_modules/express-session": { + "version": "1.18.1", + "resolved": "https://registry.npmjs.org/express-session/-/express-session-1.18.1.tgz", + "integrity": "sha512-a5mtTqEaZvBCL9A9aqkrtfz+3SMDhOVUnjafjo+s7A9Txkq+SVX2DLvSp1Zrv4uCXa3lMSK3viWnh9Gg07PBUA==", + "license": "MIT", + "dependencies": { + "cookie": "0.7.2", + "cookie-signature": "1.0.7", + "debug": "2.6.9", + "depd": "~2.0.0", + "on-headers": "~1.0.2", + "parseurl": "~1.3.3", + "safe-buffer": "5.2.1", + "uid-safe": "~2.1.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/express-session/node_modules/cookie": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/express-session/node_modules/cookie-signature": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.7.tgz", + "integrity": "sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA==", + "license": "MIT" + }, "node_modules/finalhandler": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz", "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==", + "license": "MIT", "dependencies": { "debug": "2.6.9", "encodeurl": "~2.0.0", @@ -607,15 +760,11 @@ "version": "0.5.2", "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "license": "MIT", "engines": { "node": ">= 0.6" } }, - "node_modules/fs": { - "version": "0.0.1-security", - "resolved": "https://registry.npmjs.org/fs/-/fs-0.0.1-security.tgz", - "integrity": "sha512-3XY9e1pP0CVEUCdj5BmfIZxRBTSDycnbqhIOGec9QYtmVH2fbLpj86CFWkrNOkt/Fvty4KZG5lTglL9j/gJ87w==" - }, "node_modules/fs-minipass": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", @@ -650,6 +799,7 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -679,6 +829,7 @@ "version": "1.2.4", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", + "license": "MIT", "dependencies": { "es-errors": "^1.3.0", "function-bind": "^1.1.2", @@ -718,6 +869,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", + "license": "MIT", "dependencies": { "get-intrinsic": "^1.1.3" }, @@ -729,6 +881,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "license": "MIT", "dependencies": { "es-define-property": "^1.0.0" }, @@ -740,6 +893,7 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.3.tgz", "integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==", + "license": "MIT", "engines": { "node": ">= 0.4" }, @@ -751,6 +905,7 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", + "license": "MIT", "engines": { "node": ">= 0.4" }, @@ -768,6 +923,7 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "license": "MIT", "dependencies": { "function-bind": "^1.1.2" }, @@ -775,10 +931,20 @@ "node": ">= 0.4" } }, + "node_modules/helmet": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/helmet/-/helmet-8.0.0.tgz", + "integrity": "sha512-VyusHLEIIO5mjQPUI1wpOAEu+wl6Q0998jzTxqUYGE45xCIcAxy3MsbEK/yyJUJ3ADeMoB6MornPH6GMWAf+Pw==", + "license": "MIT", + "engines": { + "node": ">=18.0.0" + } + }, "node_modules/http-errors": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "license": "MIT", "dependencies": { "depd": "2.0.0", "inherits": "2.0.4", @@ -830,6 +996,7 @@ "version": "0.4.24", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "license": "MIT", "dependencies": { "safer-buffer": ">= 2.1.2 < 3" }, @@ -1018,6 +1185,7 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", + "license": "MIT", "funding": { "url": "https://github.com/sponsors/sindresorhus" } @@ -1034,6 +1202,7 @@ "version": "1.6.0", "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "license": "MIT", "bin": { "mime": "cli.js" }, @@ -1374,6 +1543,7 @@ "version": "1.13.3", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.3.tgz", "integrity": "sha512-kDCGIbxkDSXE3euJZZXzc6to7fCrKHNI/hSRQnRuQ+BWjFNzZwiFF8fj/6o2t2G9/jTj8PSIYTfCLelLZEeRpA==", + "license": "MIT", "engines": { "node": ">= 0.4" }, @@ -1385,6 +1555,7 @@ "version": "2.4.1", "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "license": "MIT", "dependencies": { "ee-first": "1.1.1" }, @@ -1392,6 +1563,15 @@ "node": ">= 0.8" } }, + "node_modules/on-headers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz", + "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", @@ -1409,15 +1589,6 @@ "node": ">= 0.8" } }, - "node_modules/path": { - "version": "0.12.7", - "resolved": "https://registry.npmjs.org/path/-/path-0.12.7.tgz", - "integrity": "sha512-aXXC6s+1w7otVF9UletFkFcDsJeO7lSZBPUQhtb5O0xJe8LtYhj/GxldoL09bBj9+ZmE2hNoHqQSFMN5fikh4Q==", - "dependencies": { - "process": "^0.11.1", - "util": "^0.10.3" - } - }, "node_modules/path-is-absolute": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", @@ -1430,15 +1601,8 @@ "node_modules/path-to-regexp": { "version": "0.1.10", "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.10.tgz", - "integrity": "sha512-7lf7qcQidTku0Gu3YDPc8DJ1q7OOucfa/BSsIwjuh56VU7katFvuM8hULfkwB3Fns/rsVF7PwPKVw1sl5KQS9w==" - }, - "node_modules/process": { - "version": "0.11.10", - "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", - "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==", - "engines": { - "node": ">= 0.6.0" - } + "integrity": "sha512-7lf7qcQidTku0Gu3YDPc8DJ1q7OOucfa/BSsIwjuh56VU7katFvuM8hULfkwB3Fns/rsVF7PwPKVw1sl5KQS9w==", + "license": "MIT" }, "node_modules/process-nextick-args": { "version": "2.0.1", @@ -1471,6 +1635,7 @@ "version": "6.13.0", "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", + "license": "BSD-3-Clause", "dependencies": { "side-channel": "^1.0.6" }, @@ -1481,10 +1646,20 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/random-bytes": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/random-bytes/-/random-bytes-1.0.0.tgz", + "integrity": "sha512-iv7LhNVO047HzYR3InF6pUcUsPQiHTM1Qal51DcGSuZFBil1aBBWG5eHPNek7bvILMaYJ/8RU1e8w1AMdHmLQQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/range-parser": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "license": "MIT", "engines": { "node": ">= 0.6" } @@ -1493,6 +1668,7 @@ "version": "2.5.2", "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", + "license": "MIT", "dependencies": { "bytes": "3.1.2", "http-errors": "2.0.0", @@ -1540,6 +1716,12 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/rndm": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/rndm/-/rndm-1.2.0.tgz", + "integrity": "sha512-fJhQQI5tLrQvYIYFpOnFinzv9dwmR7hRnUz1XqP3OJ1jIweTNOd6aTO4jwQSgcBSFUB+/KHJxuGneime+FdzOw==", + "license": "MIT" + }, "node_modules/safe-buffer": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", @@ -1562,7 +1744,8 @@ "node_modules/safer-buffer": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "license": "MIT" }, "node_modules/semver": { "version": "7.6.3", @@ -1580,6 +1763,7 @@ "version": "0.19.0", "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz", "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==", + "license": "MIT", "dependencies": { "debug": "2.6.9", "depd": "2.0.0", @@ -1603,6 +1787,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "license": "MIT", "engines": { "node": ">= 0.8" } @@ -1610,12 +1795,14 @@ "node_modules/send/node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" }, "node_modules/serve-static": { "version": "1.16.2", "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz", "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==", + "license": "MIT", "dependencies": { "encodeurl": "~2.0.0", "escape-html": "~1.0.3", @@ -1636,6 +1823,7 @@ "version": "1.2.2", "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", + "license": "MIT", "dependencies": { "define-data-property": "^1.1.4", "es-errors": "^1.3.0", @@ -1651,12 +1839,14 @@ "node_modules/setprototypeof": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", - "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "license": "ISC" }, "node_modules/side-channel": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz", "integrity": "sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==", + "license": "MIT", "dependencies": { "call-bind": "^1.0.7", "es-errors": "^1.3.0", @@ -1704,6 +1894,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "license": "MIT", "engines": { "node": ">= 0.8" } @@ -1790,6 +1981,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "license": "MIT", "engines": { "node": ">=0.6" } @@ -1806,6 +1998,15 @@ "node": ">=14" } }, + "node_modules/tsscmp": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/tsscmp/-/tsscmp-1.0.6.tgz", + "integrity": "sha512-LxhtAkPDTkVCMQjt2h6eBVY28KCjikZqZfMcC15YBeNjkgUpdCfBu5HoiOTDu86v6smE8yOjyEktJ8hlbANHQA==", + "license": "MIT", + "engines": { + "node": ">=0.6.x" + } + }, "node_modules/type-is": { "version": "1.6.18", "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", @@ -1824,33 +2025,33 @@ "integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==", "license": "MIT" }, + "node_modules/uid-safe": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/uid-safe/-/uid-safe-2.1.5.tgz", + "integrity": "sha512-KPHm4VL5dDXKz01UuEd88Df+KzynaohSL9fBh096KWAxSKZQDI2uBrVqtvRM4rwrIrRRKsdLNML/lnaaVSRioA==", + "license": "MIT", + "dependencies": { + "random-bytes": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/unpipe": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "license": "MIT", "engines": { "node": ">= 0.8" } }, - "node_modules/util": { - "version": "0.10.4", - "resolved": "https://registry.npmjs.org/util/-/util-0.10.4.tgz", - "integrity": "sha512-0Pm9hTQ3se5ll1XihRic3FDIku70C+iHUdT/W926rSgHV5QgXsYbKZN8MSC3tJtSkhuROzvsQjAaFENRXr+19A==", - "dependencies": { - "inherits": "2.0.3" - } - }, "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", "license": "MIT" }, - "node_modules/util/node_modules/inherits": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw==" - }, "node_modules/utils-merge": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", diff --git a/package.json b/package.json index 65acf03..99c1e6e 100644 --- a/package.json +++ b/package.json @@ -15,14 +15,16 @@ "bcrypt": "^5.1.1", "cookie-parser": "^1.4.7", "cors": "^2.8.5", + "csurf": "^1.10.0", "date-fns": "^3.6.0", "dotenv": "^16.4.5", "express": "^4.21.1", - "fs": "^0.0.1-security", + "express-rate-limit": "^7.4.1", + "express-session": "^1.18.1", + "helmet": "^8.0.0", "jsonwebtoken": "^9.0.2", "mongoose": "^8.6.0", "multer": "^1.4.5-lts.1", - "path": "^0.12.7", "slugify": "^1.6.6", "uuid": "^10.0.0" } diff --git a/routes/address.route.js b/routes/address.route.js index 8447789..1429f68 100644 --- a/routes/address.route.js +++ b/routes/address.route.js @@ -1,5 +1,7 @@ const router = require("express").Router(); const verifyJWT = require("../middleware/verifyJWT.middleware"); +const limiter = require("../middleware/rateLimit.middleware"); +const csrfProtection = require("../middleware/csrf.middleware"); const { addAnAddress, getUserAddresses, @@ -7,9 +9,20 @@ const { deleteAnAddress, } = require("../controllers/address.controller"); -router.route("/add-an-address").post(verifyJWT, addAnAddress); -router.route("/get-user-addresses").get(verifyJWT, getUserAddresses); -router.route("/update/:id").put(verifyJWT, updateAnAddress); -router.route("/delete/:id").delete(verifyJWT, deleteAnAddress); +router + .route("/add-an-address") + .post(limiter("15m", 100), verifyJWT, addAnAddress); + +router + .route("/get-user-addresses") + .get(limiter("15m", 100), verifyJWT, getUserAddresses); + +router + .route("/update/:id") + .put(limiter("15m", 100), csrfProtection, verifyJWT, updateAnAddress); + +router + .route("/delete/:id") + .delete(limiter("15m", 100), csrfProtection, verifyJWT, deleteAnAddress); module.exports = router; diff --git a/routes/attribute.route.js b/routes/attribute.route.js index 5309500..152770b 100644 --- a/routes/attribute.route.js +++ b/routes/attribute.route.js @@ -2,6 +2,8 @@ const router = require("express").Router(); const verifyJWT = require("../middleware/verifyJWT.middleware"); const verifyRoles = require("../middleware/verifyRoles.middleware"); const ROLES_LIST = require("../config/rolesList"); +const csrfProtection = require("../middleware/csrf.middleware"); +const limiter = require("../middleware/rateLimit.middleware"); const { createAttribute, getAllAttributes, @@ -10,39 +12,53 @@ const { deleteAnAttribute, } = require("../controllers/attribute.controller"); -router.post( - "/create-attribute", - verifyJWT, - verifyRoles(ROLES_LIST.ADMIN, ROLES_LIST.MANAGER), - createAttribute -); +router + .route("/create-attribute") + .post( + limiter("15m", 100), + csrfProtection, + verifyJWT, + verifyRoles(ROLES_LIST.ADMIN, ROLES_LIST.MANAGER), + createAttribute + ); -router.get( - "/get-all-attributes", - verifyJWT, - verifyRoles(ROLES_LIST.ADMIN, ROLES_LIST.MANAGER), - getAllAttributes -); +router + .route("/get-all-attributes") + .get( + limiter("15m", 100), + verifyJWT, + verifyRoles(ROLES_LIST.ADMIN, ROLES_LIST.MANAGER), + getAllAttributes + ); -router.get( - "/get-an-attribute/:id", - verifyJWT, - verifyRoles(ROLES_LIST.ADMIN, ROLES_LIST.MANAGER), - getAnAttribute -); +router + .route("/get-an-attribute/:id") + .get( + limiter("15m", 100), + csrfProtection, + verifyJWT, + verifyRoles(ROLES_LIST.ADMIN, ROLES_LIST.MANAGER), + getAnAttribute + ); -router.patch( - "/update-an-attribute/:id", - verifyJWT, - verifyRoles(ROLES_LIST.ADMIN, ROLES_LIST.MANAGER), - updateAnAttribute -); +router + .route("/update-an-attribute/:id") + .patch( + limiter("15m", 100), + csrfProtection, + verifyJWT, + verifyRoles(ROLES_LIST.ADMIN, ROLES_LIST.MANAGER), + updateAnAttribute + ); -router.delete( - "/delete-an-attribute/:id", - verifyJWT, - verifyRoles(ROLES_LIST.ADMIN, ROLES_LIST.MANAGER), - deleteAnAttribute -); +router + .route("/delete-an-attribute/:id") + .delete( + limiter("15m", 100), + csrfProtection, + verifyJWT, + verifyRoles(ROLES_LIST.ADMIN, ROLES_LIST.MANAGER), + deleteAnAttribute + ); module.exports = router; diff --git a/routes/cart.route.js b/routes/cart.route.js index e5f22c9..7263f3e 100644 --- a/routes/cart.route.js +++ b/routes/cart.route.js @@ -7,19 +7,28 @@ const { getCartOfACustomer, } = require("../controllers/cart.controller"); const ROLES_LIST = require("../config/rolesList"); -const varifyRoles = require("../middleware/verifyRoles.middleware"); +const verifyRoles = require("../middleware/verifyRoles.middleware"); +const limiter = require("../middleware/rateLimit.middleware"); +const csrfProtection = require("../middleware/csrf.middleware"); + +router + .route("/create-a-cart") + .post(limiter("15m", 100), csrfProtection, verifyJWT, createACart); + +router + .route("/add-items-to-cart") + .patch(limiter("15m", 100), csrfProtection, verifyJWT, addItemsToCart); -router.route("/create-a-cart").post(verifyJWT, createACart); -router.route("/add-items-to-cart").patch(verifyJWT, addItemsToCart); router .route("/get-all-carts") .get( + limiter("15m", 100), verifyJWT, - varifyRoles(ROLES_LIST.ADMIN, ROLES_LIST.MANAGER), + verifyRoles(ROLES_LIST.ADMIN, ROLES_LIST.MANAGER), getAllCarts ); router .route("/get-cart-of-a-customer/:customerId") - .get(verifyJWT, getCartOfACustomer); + .get(limiter("15m", 100), verifyJWT, getCartOfACustomer); module.exports = router; diff --git a/routes/category.route.js b/routes/category.route.js index 76cba6b..6abeb7b 100644 --- a/routes/category.route.js +++ b/routes/category.route.js @@ -9,11 +9,15 @@ const router = express.Router(); const upload = require("../middleware/multer.middleware"); const verifyJWT = require("../middleware/verifyJWT.middleware"); const verifyRoles = require("../middleware/verifyRoles.middleware"); +const limiter = require("../middleware/rateLimit.middleware"); +const csrfProtection = require("../middleware/csrf.middleware"); const ROLES_LIST = require("../config/rolesList"); router .route("/create-category") .post( + limiter("15m", 100), + csrfProtection, verifyJWT, verifyRoles(ROLES_LIST.ADMIN, ROLES_LIST.MANAGER), upload.single("thumbnail"), @@ -23,6 +27,7 @@ router router .route("/get-categories") .get( + limiter("15m", 100), verifyJWT, verifyRoles(ROLES_LIST.ADMIN, ROLES_LIST.MANAGER), getCategories @@ -31,6 +36,8 @@ router router .route("/delete-category/:slug") .delete( + limiter("15m", 100), + csrfProtection, verifyJWT, verifyRoles(ROLES_LIST.ADMIN, ROLES_LIST.MANAGER), deleteCategory @@ -39,6 +46,8 @@ router router .route("/update-category/:slug") .patch( + limiter("15m", 100), + csrfProtection, verifyJWT, verifyRoles(ROLES_LIST.ADMIN, ROLES_LIST.MANAGER), upload.single("thumbnail"), diff --git a/routes/product.route.js b/routes/product.route.js new file mode 100644 index 0000000..3f4c4da --- /dev/null +++ b/routes/product.route.js @@ -0,0 +1,76 @@ +const ROLES_LIST = require("../config/rolesList"); +const { + addAProduct, + getAllProducts, + getAProduct, + updateAProduct, + updateProductThumbnail, + updateProductGallery, + deleteAProduct, +} = require("../controllers/product.controller"); +const upload = require("../middleware/multer.middleware"); +const verifyJWT = require("../middleware/verifyJWT.middleware"); +const verifyRoles = require("../middleware/verifyRoles.middleware"); +const limiter = require("../middleware/rateLimit.middleware"); +const csrfProtection = require("../middleware/csrf.middleware"); + +const router = require("express").Router(); + +router + .route("/add-a-product") + .post( + limiter("15m", 100), + csrfProtection, + verifyJWT, + verifyRoles(ROLES_LIST.ADMIN, ROLES_LIST.MANAGER), + upload.fields([{ name: "thumbnail", maxCount: 1 }, { name: "gallery" }]), + addAProduct + ); + +router.route("/get-all-products").get(limiter("15m", 100), getAllProducts); + +router.route("/get-a-product/:id").get(limiter("15m", 100), getAProduct); + +router + .route("/update-a-product/:id") + .patch( + limiter("15m", 100), + csrfProtection, + verifyJWT, + verifyRoles(ROLES_LIST.ADMIN, ROLES_LIST.MANAGER), + updateAProduct + ); + +router + .route("/update-product-thumbnail/:id") + .patch( + limiter("15m", 100), + csrfProtection, + verifyJWT, + verifyRoles(ROLES_LIST.ADMIN, ROLES_LIST.MANAGER), + upload.single("thumbnail"), + updateProductThumbnail + ); + +router + .route("/update-product-gallery/:id") + .patch( + limiter("15m", 100), + csrfProtection, + verifyJWT, + verifyRoles(ROLES_LIST.ADMIN, ROLES_LIST.MANAGER), + upload.array("gallery", 10), + updateProductGallery + ); + +router + .route("/delete-a-product/:id") + .delete( + limiter("15m", 100), + csrfProtection, + verifyJWT, + verifyRoles(ROLES_LIST.ADMIN), + deleteAProduct + ); + +module.exports = router; diff --git a/routes/product.routes.js b/routes/product.routes.js deleted file mode 100644 index 387c6e2..0000000 --- a/routes/product.routes.js +++ /dev/null @@ -1,39 +0,0 @@ -const ROLES_LIST = require("../config/rolesList"); -const { - addAProduct, - getAllProducts, - getAProduct, - updateAProduct, - deleteAProduct, -} = require("../controllers/product.controller"); -const upload = require("../middleware/multer.middleware"); -const verifyJWT = require("../middleware/verifyJWT.middleware"); -const verifyRoles = require("../middleware/verifyRoles.middleware"); - -const router = require("express").Router(); - -router - .route("/add-a-product") - .post( - verifyJWT, - verifyRoles(ROLES_LIST.ADMIN, ROLES_LIST.MANAGER), - upload.fields([{ name: "thumbnail", maxCount: 1 }, { name: "gallery" }]), - addAProduct - ); - -router.route("/get-all-products").get(getAllProducts); -router.route("/get-a-product/:id").get(getAProduct); - -router - .route("/update-a-product/:id") - .patch( - verifyJWT, - verifyRoles(ROLES_LIST.ADMIN, ROLES_LIST.MANAGER), - updateAProduct - ); - -router - .route("/delete-a-product/:id") - .delete(verifyJWT, verifyRoles(ROLES_LIST.ADMIN), deleteAProduct); - -module.exports = router; diff --git a/routes/roles.route.js b/routes/roles.route.js index 266f962..f7dd0a2 100644 --- a/routes/roles.route.js +++ b/routes/roles.route.js @@ -2,6 +2,8 @@ const router = require("express").Router(); const ROLES_LIST = require("../config/rolesList"); const verifyJWT = require("../middleware/verifyJWT.middleware"); const verifyRoles = require("../middleware/verifyRoles.middleware"); +const limiter = require("../middleware/rateLimit.middleware"); +const csrfProtection = require("../middleware/csrf.middleware"); const { assignRoleToUser, @@ -11,6 +13,8 @@ const { router .route("/assign-roles/:id") .patch( + limiter("15m", 100), + csrfProtection, verifyJWT, verifyRoles(ROLES_LIST.ADMIN), assignRoleToUser @@ -19,9 +23,11 @@ router router .route("/revoke-roles/:id") .patch( + limiter("15m", 100), + csrfProtection, verifyJWT, verifyRoles(ROLES_LIST.ADMIN), revokeRoleFromUser ); -module.exports = router; \ No newline at end of file +module.exports = router; diff --git a/routes/tag.route.js b/routes/tag.route.js index fe59587..2a2c27b 100644 --- a/routes/tag.route.js +++ b/routes/tag.route.js @@ -9,11 +9,15 @@ const { const upload = require("../middleware/multer.middleware"); const verifyJWT = require("../middleware/verifyJWT.middleware"); const verifyRoles = require("../middleware/verifyRoles.middleware"); +const limiter = require("../middleware/rateLimit.middleware"); +const csrfProtection = require("../middleware/csrf.middleware"); const ROLES_LIST = require("../config/rolesList"); router .route("/create-tag") .post( + limiter("15m", 100), + csrfProtection, verifyJWT, verifyRoles(ROLES_LIST.ADMIN, ROLES_LIST.MANAGER), createTag @@ -21,15 +25,27 @@ router router .route("/get-all-tags") - .get(verifyJWT, verifyRoles(ROLES_LIST.ADMIN, ROLES_LIST.MANAGER), getTags); + .get( + limiter("15m", 100), + verifyJWT, + verifyRoles(ROLES_LIST.ADMIN, ROLES_LIST.MANAGER), + getTags + ); router .route("/get-tag/:name") - .get(verifyJWT, verifyRoles(ROLES_LIST.ADMIN, ROLES_LIST.MANAGER), getATag); + .get( + limiter("15m", 100), + verifyJWT, + verifyRoles(ROLES_LIST.ADMIN, ROLES_LIST.MANAGER), + getATag + ); router .route("/update-tag/:id") .patch( + limiter("15m", 100), + csrfProtection, verifyJWT, verifyRoles(ROLES_LIST.ADMIN, ROLES_LIST.MANAGER), updateTag @@ -38,6 +54,8 @@ router router .route("/delete-tag/:name") .delete( + limiter("15m", 100), + csrfProtection, verifyJWT, verifyRoles(ROLES_LIST.ADMIN, ROLES_LIST.MANAGER), deleteTag diff --git a/routes/user.route.js b/routes/user.route.js index 4749201..bce1b15 100644 --- a/routes/user.route.js +++ b/routes/user.route.js @@ -10,35 +10,66 @@ const { getUsersByRole, } = require("../controllers/user.controller"); const ROLES_LIST = require("../config/rolesList"); - +const limiter = require("../middleware/rateLimit.middleware"); const upload = require("../middleware/multer.middleware"); const verifyJWT = require("../middleware/verifyJWT.middleware"); const verifyRoles = require("../middleware/verifyRoles.middleware"); +const csrfProtection = require("../middleware/csrf.middleware"); const router = require("express").Router(); router .route("/register") - .post(upload.single("avatar"), registerUser); -router.route("/login").post(loginUser); // if using form data in frontend then add upload.none() middleware in the route -// or check:=> Content-Type multipart/form-data (in postman) -// or send data in form data in postman as: x-www-form-urlencoded - + .post( + limiter("15m", 100), + csrfProtection, + upload.single("avatar"), + registerUser + ); +router.route("/login").post(limiter("15m", 100), csrfProtection, loginUser); //Secured Routes -router.route("/logout").post(verifyJWT, logout); -router.route("/refresh-access-token").get(verifyJWT, refreshAccessToken); -router.route("/update-user").patch(verifyJWT, updateUserInfo); +router + .route("/logout") + .post(limiter("15m", 100), csrfProtection, verifyJWT, logout); + +router + .route("/refresh-access-token") + .get(limiter("15m", 100), verifyJWT, refreshAccessToken); + +router + .route("/update-user") + .patch(limiter("15m", 100), csrfProtection, verifyJWT, updateUserInfo); + router .route("/update-username") - .patch(verifyJWT, updateUsername); + .patch(limiter("15m", 100), csrfProtection, verifyJWT, updateUsername); + router .route("/update-avatar") - .post(verifyJWT, upload.single("avatar"), updateAvatar); + .post( + limiter("15m", 100), + csrfProtection, + verifyJWT, + upload.single("avatar"), + updateAvatar + ); + router .route("/get-all-users") - .get(verifyJWT, verifyRoles(ROLES_LIST.ADMIN), getAllUsers); + .get( + limiter("15m", 100), + verifyJWT, + verifyRoles(ROLES_LIST.ADMIN), + getAllUsers + ); + router .route("/get-users") - .get(verifyJWT, verifyRoles(ROLES_LIST.ADMIN), getUsersByRole); + .get( + limiter("15m", 100), + verifyJWT, + verifyRoles(ROLES_LIST.ADMIN), + getUsersByRole + ); module.exports = router; diff --git a/routes/variation.route.js b/routes/variation.route.js index aea1d68..3f453d5 100644 --- a/routes/variation.route.js +++ b/routes/variation.route.js @@ -10,11 +10,15 @@ const { } = require("../controllers/variation.controller"); const verifyJWT = require("../middleware/verifyJWT.middleware"); const verifyRoles = require("../middleware/verifyRoles.middleware"); +const limiter = require("../middleware/rateLimit.middleware"); +const csrfProtection = require("../middleware/csrf.middleware"); const ROLES_LIST = require("../config/rolesList"); router .route("/add-a-variation/:id") .post( + limiter("15m", 100), + csrfProtection, verifyJWT, verifyRoles(ROLES_LIST.ADMIN, ROLES_LIST.MANAGER), addAVariation @@ -22,15 +26,23 @@ router router .route("/get-all-variations-of-a-product/:id") - .get(getAllVariationsOfAProduct); + .get(limiter("15m", 100), getAllVariationsOfAProduct); router .route("/delete-a-variation/:id") - .delete(verifyJWT, verifyRoles(ROLES_LIST.ADMIN), deleteAVariation); + .delete( + limiter("15m", 100), + csrfProtection, + verifyJWT, + verifyRoles(ROLES_LIST.ADMIN), + deleteAVariation + ); router .route("/delete-all-variations-of-a-product/:id") .delete( + limiter("15m", 100), + csrfProtection, verifyJWT, verifyRoles(ROLES_LIST.ADMIN), deleteAllVariationsOfAProduct @@ -39,6 +51,8 @@ router router .route("/update-a-variation/:id") .patch( + limiter("15m", 100), + csrfProtection, verifyJWT, verifyRoles(ROLES_LIST.ADMIN, ROLES_LIST.MANAGER), updateAVariation diff --git a/utils/ApiResponse.js b/utils/ApiResponse.js index b5e583f..a9821e4 100644 --- a/utils/ApiResponse.js +++ b/utils/ApiResponse.js @@ -47,6 +47,13 @@ class ApiResponse { }); } + static tooManyRequests(res, message = "Too many requests", statusCode = 429) { + return res.status(statusCode).json({ + status: "fail", + message, + }); + } + static error(res, message, statusCode = 500, error = null) { return res.status(statusCode).json({ status: "error",