diff --git a/backend/middleware/validator.js b/backend/middleware/validator.js new file mode 100644 index 0000000..5dbce52 --- /dev/null +++ b/backend/middleware/validator.js @@ -0,0 +1,10 @@ +import { validationResult } from "express-validator" + +const validator = (req, res, next) => { + const result = validationResult(req); + if(result.isEmpty()) + return next(); + res.status(400).json({ errors: result.array() }); +} + +export default validator; \ No newline at end of file diff --git a/backend/routes/orderRoutes.js b/backend/routes/orderRoutes.js index 8a0afe5..b44fb20 100644 --- a/backend/routes/orderRoutes.js +++ b/backend/routes/orderRoutes.js @@ -1,5 +1,4 @@ import express from 'express'; -const router = express.Router(); import { protect, admin } from '../middleware/authMiddleware.js'; import { addOrderItems, @@ -9,12 +8,55 @@ import { updateOrderToDeliver, getOrders } from '../controllers/orderController.js'; +import validateRequest from '../middleware/validator.js'; +import { param, check } from 'express-validator'; + +const router = express.Router(); + +const validator = { + getOrderById: [ + param('id').notEmpty().withMessage('Id is required').isMongoId().withMessage('Invalid Id Format') + ], + updateOrderToPaid: [ + param('id').notEmpty().withMessage('Id is required').isMongoId().withMessage('Invalid Id Format') + ], + updateOrderToDeliver: [ + param('id').notEmpty().withMessage('Id is required').isMongoId().withMessage('Invalid Id Format') + ], + addOrderItems: [ + check('cartItems').notEmpty().withMessage('Cart items are required'), + check('shippingAddress').notEmpty().withMessage('Shipping address is required'), + check('paymentMethod').notEmpty().withMessage('Payment method is required'), + check('itemsPrice') + .notEmpty() + .withMessage('Items price is required') + .isNumeric() + .withMessage('Items price must be a number'), + check('taxPrice') + .notEmpty() + .withMessage('Tax price is required') + .isNumeric() + .withMessage('Tax price must be a number'), + check('shippingPrice') + .notEmpty() + .withMessage('Shipping price is required') + .isNumeric() + .withMessage('Shipping price must be a number'), + check('totalPrice') + .notEmpty() + .withMessage('Total price is required') + .isNumeric() + .withMessage('Total price must be a number') + ] +} -router.route('/').post(protect, addOrderItems).get(protect, admin, getOrders); +router.route('/') + .post(validator.addOrderItems, validateRequest, protect, addOrderItems) + .get(protect, admin, getOrders); router.get('/my-orders', protect, getMyOrders); -router.get('/:id', protect, getOrderById); -router.put('/:id/pay', protect, updateOrderToPaid); -router.put('/:id/deliver', protect, admin, updateOrderToDeliver); +router.get('/:id', validator.getOrderById, validateRequest, protect, getOrderById); +router.put('/:id/pay', validator.updateOrderToPaid, validateRequest, protect, updateOrderToPaid); +router.put('/:id/deliver', validator.updateOrderToDeliver, validateRequest, protect, admin, updateOrderToDeliver); export default router; diff --git a/backend/routes/paymentRoutes.js b/backend/routes/paymentRoutes.js index 6ad9c0c..75ad48b 100644 --- a/backend/routes/paymentRoutes.js +++ b/backend/routes/paymentRoutes.js @@ -2,13 +2,40 @@ import express from 'express'; import { protect } from '../middleware/authMiddleware.js'; import { config, order, validate } from '../controllers/paymentController.js'; +import validateRequest from '../middleware/validator.js'; +import {body, check} from 'express-validator'; const router = express.Router(); +const validator = { + order: [ + body().custom(body => { + if (Object.keys(body).length === 0) + throw new Error('Request Body is empty') + return true; + }) + ], + validate: [ + body('razorpay_order_id') + .notEmpty() + .withMessage('Razorpay order ID is required') + .trim(), + body('razorpay_payment_id') + .notEmpty() + .withMessage('Razorpay payment ID is required') + .trim(), + body('razorpay_signature') + .notEmpty() + .withMessage('Razorpay signature is required') + .trim() + .escape() + ] +} + router.get('/razorpay/config', config); -router.post('/razorpay/order', protect, order); +router.post('/razorpay/order', validator.order, validateRequest, protect, order); -router.post('/razorpay/order/validate', protect, validate); +router.post('/razorpay/order/validate', validator.validate, validateRequest, protect, validate); export default router; diff --git a/backend/routes/productRoutes.js b/backend/routes/productRoutes.js index 67f48c0..b462983 100644 --- a/backend/routes/productRoutes.js +++ b/backend/routes/productRoutes.js @@ -1,4 +1,4 @@ -import express from 'express'; +import express, { query } from 'express'; import { getProducts, getProduct, @@ -9,16 +9,88 @@ import { getTopProducts } from '../controllers/productController.js'; import { protect, admin } from '../middleware/authMiddleware.js'; +import validateRequest from '../middleware/validator.js'; +import {body, check, param} from 'express-validator'; const router = express.Router(); -router.route('/').post(protect, admin, createProduct).get(getProducts); +const validator = { + getProducts: [ + check('limit').optional().isNumeric().withMessage('Limit parameter must be a number').custom(value => { + if(value < 0) throw new Error('Value should not be less than Zero'); + return true; + }), + check('skip').optional().isNumeric().withMessage('skip parameter must be a number').custom(value => { + if(value < 0) throw new Error('Value should not be less than Zero'); + return true; + }), + check('search').optional().trim().escape() + ], + createProduct: [ + check('name').trim().notEmpty().withMessage('Name is required').escape(), + check('image').notEmpty().withMessage('Image is required'), + check('description') + .trim() + .notEmpty() + .withMessage('Description is required') + .escape(), + check('brand').trim().notEmpty().withMessage('Brand is required').escape(), + check('category').trim().notEmpty().withMessage('Category is required').escape(), + check('price') + .notEmpty() + .withMessage('Price is required') + .isNumeric() + .withMessage('Price must be a number'), + check('countInStock') + .notEmpty() + .withMessage('Count in stock is required') + .isNumeric() + .withMessage('Count in stock must be a number') + ], + createProductReview: [ + param('id').notEmpty().withMessage('Id is required').isMongoId().withMessage('Invalid Id Format'), + body('rating').notEmpty().withMessage('Rating is Empty').bail().isNumeric().withMessage('Ratings must be number'), + body('comment').trim().escape() + ], + getProduct: [ + param('id').notEmpty().withMessage('Id is required').isMongoId().withMessage('Invalid Id Format') + ], + deleteProduct: [ + param('id').notEmpty().withMessage('Id is required').isMongoId().withMessage('Invalid Id Format') + ], + updateProduct: [ + check('name').trim().notEmpty().withMessage('Name is required').escape(), + check('image').notEmpty().withMessage('Image is required'), + check('description') + .trim() + .notEmpty() + .withMessage('Description is required') + .escape(), + check('brand').trim().notEmpty().withMessage('Brand is required').escape(), + check('category').trim().notEmpty().withMessage('Category is required').escape(), + check('price') + .notEmpty() + .withMessage('Price is required') + .isNumeric() + .withMessage('Price must be a number'), + check('countInStock') + .notEmpty() + .withMessage('Count in stock is required') + .isNumeric() + .withMessage('Count in stock must be a number'), + param('id').notEmpty().withMessage('Id is required').isMongoId().withMessage('Invalid Id Format') + ] +} + +router.route('/') + .post(validator.createProduct, validateRequest, protect, admin, createProduct) + .get(validator.getProducts, validateRequest, getProducts); router.get('/top', getTopProducts); -router.post('/reviews/:id', protect, createProductReview); +router.post('/reviews/:id', validator.createProductReview, validateRequest, protect, createProductReview); router .route('/:id') - .get(getProduct) - .put(protect, admin, updateProduct) - .delete(protect, admin, deleteProduct); + .get(validator.getProduct, validateRequest, getProduct) + .put(validator.updateProduct, validateRequest, protect, admin, updateProduct) + .delete(validator.deleteProduct, validateRequest, protect, admin, deleteProduct); export default router; diff --git a/backend/routes/uploadRoutes.js b/backend/routes/uploadRoutes.js index 7178d83..512bdb1 100644 --- a/backend/routes/uploadRoutes.js +++ b/backend/routes/uploadRoutes.js @@ -1,5 +1,6 @@ -import express from 'express'; +import express from 'express';import { body, check } from 'express-validator'; import multer from 'multer'; +import validateRequest from '../middleware/validator.js'; const router = express.Router(); @@ -13,7 +14,7 @@ const storage = multer.diskStorage({ }); const fileFilter = (req, file, cb) => { - if ( + if ( file.mimetype === 'image/png' || file.mimetype === 'image/jpg' || file.mimetype === 'image/jpeg' @@ -21,7 +22,7 @@ const fileFilter = (req, file, cb) => { // To accept the file pass `true`, like so: cb(null, true); } else { - // To reject this file pass `false`, like so: + // To reject this file pass `false`, like so: cb('Images only!'); } }; @@ -29,7 +30,9 @@ const fileFilter = (req, file, cb) => { const upload = multer({ storage, fileFilter }).single('image'); router.post('/', upload, (req, res) => { - console.log(req.file); + if (!req.file) + throw res.status(400).json({error: 'No file uploaded'}); + res.send({ message: 'Image uploaded', imageUrl: `/${req.file.path}` diff --git a/backend/routes/userRoutes.js b/backend/routes/userRoutes.js index 6fc71d2..bf95ddf 100644 --- a/backend/routes/userRoutes.js +++ b/backend/routes/userRoutes.js @@ -1,5 +1,4 @@ import express from 'express'; -const router = express.Router(); import { loginUser, registerUser, @@ -15,24 +14,61 @@ import { resetPassword } from '../controllers/userController.js'; import { protect, admin } from '../middleware/authMiddleware.js'; +import validateRequest from '../middleware/validator.js'; +import {body, param} from 'express-validator'; + +const router = express.Router(); +const validator = { + checkLogin: [ + body('email').trim().notEmpty().withMessage('Email is Required').bail().isEmail().withMessage("Please enter a valid email address"), + body('password').trim().isString().notEmpty().withMessage('Password is Empty') + ], + checkNewUser: [ + body('email').trim().notEmpty().withMessage('Email is Required').bail().isEmail().withMessage("Please enter a valid email address"), + body('password').trim().isString().notEmpty().withMessage('Password is Empty').bail() + .isLength({ min: 6 }).withMessage('Password must be at least 6 characters'), + body('name').trim().notEmpty().withMessage('Name is Required').escape() + ], + checkGetUserById: [ + param('id').exists().withMessage('Id is required').isMongoId().withMessage('Invalid Id') + ], + checkUpdateUser: [ + body('email').trim().notEmpty().withMessage('Email is Required').bail().isEmail().withMessage("Please enter a valid email address"), + body('name').trim().notEmpty().withMessage('Name is Required').escape(), + body('isAdmin').isBoolean().withMessage('isAdmin value should be true/false'), + param('id').exists().withMessage('Id is required').isMongoId().withMessage('Invalid Id') + ], + resetPasswordRequest: [ + body('email').trim().notEmpty().withMessage('Email is Required').bail().isEmail().withMessage("Please enter a valid email address") + ], + resetPassword: [ + body('password').trim().notEmpty().withMessage('Password is Required').escape().bail() + .isLength({ min: 6 }).withMessage('Password must be at least 6 characters'), + param('id').exists().withMessage('Id is required').isMongoId().withMessage('Invalid Id'), + param('token').trim().notEmpty().withMessage('Token is Required') + ] +} + +router.route('/') + .post(validator.checkNewUser, validateRequest, registerUser) + .get(protect, admin, getUsers); -router.route('/').post(registerUser).get(protect, admin, getUsers); router.route('/admins').get(protect, admin, admins); -router.post('/reset-password/request', resetPasswordRequest); -router.post('/reset-password/reset/:id/:token', resetPassword); -router.post('/login', loginUser); +router.post('/reset-password/request', validator.resetPasswordRequest, validateRequest, resetPasswordRequest); +router.post('/reset-password/reset/:id/:token', validator.resetPassword, validateRequest, resetPassword); +router.post('/login', validator.checkLogin, validateRequest, loginUser); router.post('/logout', protect, logoutUser); router .route('/profile') .get(protect, getUserProfile) - .put(protect, updateUserProfile); + .put(validator.checkNewUser, validateRequest, protect, updateUserProfile); router .route('/:id') - .get(protect, admin, getUserById) - .put(protect, admin, updateUser) - .delete(protect, admin, deleteUser); + .get(validator.checkGetUserById, validateRequest, protect, admin, getUserById) + .put(validator.checkUpdateUser, validateRequest, protect, admin, updateUser) + .delete(validator.checkGetUserById, validateRequest, protect, admin, deleteUser); export default router; diff --git a/package-lock.json b/package-lock.json index 32dd0b4..c44126e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,6 +15,7 @@ "cookie-parser": "^1.4.6", "cors": "^2.8.5", "express": "^4.18.2", + "express-validator": "^7.0.1", "jsonwebtoken": "^9.0.2", "mongoose": "^8.0.3", "multer": "^1.4.5-lts.1", @@ -732,6 +733,18 @@ "node": ">= 0.10.0" } }, + "node_modules/express-validator": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/express-validator/-/express-validator-7.0.1.tgz", + "integrity": "sha512-oB+z9QOzQIE8FnlINqyIFA8eIckahC6qc8KtqLdLJcU3/phVyuhXH3bA4qzcrhme+1RYaCSwrq+TlZ/kAKIARA==", + "dependencies": { + "lodash": "^4.17.21", + "validator": "^13.9.0" + }, + "engines": { + "node": ">= 8.0.0" + } + }, "node_modules/extend": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", @@ -2241,6 +2254,14 @@ "node": ">= 0.4.0" } }, + "node_modules/validator": { + "version": "13.11.0", + "resolved": "https://registry.npmjs.org/validator/-/validator-13.11.0.tgz", + "integrity": "sha512-Ii+sehpSfZy+At5nPdnyMhx78fEoPDkR2XW/zimHEL3MyGJQOCQ7WeP20jPYRz7ZCpcKLB21NxuXHF3bxjStBQ==", + "engines": { + "node": ">= 0.10" + } + }, "node_modules/vary": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", diff --git a/package.json b/package.json index 8443b0a..bbd2214 100644 --- a/package.json +++ b/package.json @@ -22,6 +22,7 @@ "cookie-parser": "^1.4.6", "cors": "^2.8.5", "express": "^4.18.2", + "express-validator": "^7.0.1", "jsonwebtoken": "^9.0.2", "mongoose": "^8.0.3", "multer": "^1.4.5-lts.1",