From 88e2fa539f0d8da0e2adb5f9365f86bc08c783a4 Mon Sep 17 00:00:00 2001 From: HasinduOnline <40927446+hrtechplus@users.noreply.github.com> Date: Tue, 23 Sep 2025 22:35:19 +0530 Subject: [PATCH 1/3] feat(security): enhance JWT verification and input validation to prevent attacks --- middleware/auth.js | 8 +++-- middleware/security.js | 14 ++++++--- sonarqube-fixes.md | 68 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 84 insertions(+), 6 deletions(-) create mode 100644 sonarqube-fixes.md diff --git a/middleware/auth.js b/middleware/auth.js index 5bdaac0..137df82 100644 --- a/middleware/auth.js +++ b/middleware/auth.js @@ -23,8 +23,12 @@ const authenticateUser = async (req, res, next) => { }); } - // Verify token - const decoded = jwt.verify(token, process.env.JWT_SECRET_KEY); + // Verify token with specific algorithm to prevent attacks + const decoded = jwt.verify(token, process.env.JWT_SECRET_KEY, { + algorithms: ["HS256"], + issuer: "wedding-management-system", + maxAge: "1h", + }); // Get user from database const user = await User.findById(decoded.id); diff --git a/middleware/security.js b/middleware/security.js index a841cf4..159d08b 100644 --- a/middleware/security.js +++ b/middleware/security.js @@ -41,16 +41,22 @@ const validateInput = (schema) => { // Search query sanitizer const sanitizeSearchQuery = (req, res, next) => { if (req.query.search) { - // Escape special regex characters - req.query.search = req.query.search.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); - - // Limit search length + // Validate search query length first if (req.query.search.length > 100) { return res.status(400).json({ error: "Search query too long", code: "SEARCH_TOO_LONG", }); } + + // Use safer character allowlist instead of escaping + const safePattern = /^[a-zA-Z0-9\s\-_]+$/; + if (!safePattern.test(req.query.search)) { + return res.status(400).json({ + error: "Search query contains invalid characters", + code: "INVALID_SEARCH_CHARS", + }); + } } next(); }; diff --git a/sonarqube-fixes.md b/sonarqube-fixes.md new file mode 100644 index 0000000..24a4847 --- /dev/null +++ b/sonarqube-fixes.md @@ -0,0 +1,68 @@ +# SonarQube Security Fixes Applied + +## Fixed Issues: + +### 1. **Hardcoded Credentials (Critical)** + +- **Before**: MongoDB connection string with embedded credentials +- **After**: Using environment variables +- **Location**: `packageapp/backend/index.js` + +### 2. **Information Disclosure in Error Handling** + +- **Before**: Full error objects exposed to client +- **After**: Only error messages logged, generic responses to client +- **Location**: Error handlers in package operations + +### 3. **Unsafe Regular Expression (ReDoS)** + +- **Before**: Complex regex pattern vulnerable to ReDoS attacks +- **After**: Character allowlist validation +- **Location**: `middleware/security.js` + +### 4. **JWT Algorithm Confusion Attack** + +- **Before**: JWT verification without algorithm specification +- **After**: Explicit HS256 algorithm with additional security options +- **Location**: `middleware/auth.js` + +### 5. **Missing Environment Configuration** + +- **Action**: Need to create `.env` files in each backend directory + +## Required Environment Variables: + +```bash +# Create .env file in each backend directory: +NODE_ENV=development +MONGODB_URI=mongodb://localhost:27017/wedding_dev +JWT_SECRET_KEY=your-super-secret-jwt-key-minimum-32-characters +SESSION_SECRET=your-super-secret-session-key-minimum-32-characters +``` + +## Security Improvements Made: + +✅ Removed hardcoded database credentials +✅ Prevented information disclosure in error messages +✅ Fixed potential ReDoS vulnerability in search +✅ Secured JWT verification with algorithm specification +✅ Added input validation with safe character patterns + +## Next Steps for SonarQube: + +1. **Create `.env` files** with proper secrets +2. **Re-run SonarQube analysis** to verify fixes +3. **Review remaining security hotspots** manually +4. **Add code coverage** if required by quality gate + +## Security Hotspots to Review: + +The 13 security hotspots likely include: + +- Authentication mechanisms +- Input validation patterns +- Error handling procedures +- Cryptographic operations +- File upload handling + +**Note**: These fixes address the most common SonarQube security issues in Node.js applications. From 8b2172c88fe3ba2533301c235c56c997c548dbf9 Mon Sep 17 00:00:00 2001 From: HasinduOnline <40927446+hrtechplus@users.noreply.github.com> Date: Tue, 23 Sep 2025 22:41:40 +0530 Subject: [PATCH 2/3] feat(api): enhance package upload and deletion endpoints with authentication, role validation, and improved error handling --- packageapp/backend/index.js | 73 +++++++++++++++++++++++++++++++------ 1 file changed, 62 insertions(+), 11 deletions(-) diff --git a/packageapp/backend/index.js b/packageapp/backend/index.js index f4f15b0..541f3d2 100644 --- a/packageapp/backend/index.js +++ b/packageapp/backend/index.js @@ -54,11 +54,34 @@ async function run() { //insert a package to the database post method - app.post("/upload-Package", async (req, res) => { - const data = req.body; - const result = await Packagecollection.insertOne(data); - res.send(result); - }); + app.post( + "/upload-Package", + authenticateUser, + requireRole(["admin"]), + validateInput(schemas.package), + async (req, res) => { + try { + const data = { + ...req.body, + createdBy: req.user.id, + createdAt: new Date(), + updatedAt: new Date(), + }; + const result = await Packagecollection.insertOne(data); + res.json({ + success: true, + message: "Package created successfully", + id: result.insertedId, + }); + } catch (error) { + console.error("Package creation failed:", error.message); + res.status(500).json({ + error: "Failed to create package", + code: "CREATION_FAILED", + }); + } + } + ); //get all data from the database @@ -93,12 +116,40 @@ async function run() { //delete package data - app.delete("/Package/:id", async (req, res) => { - const id = req.params.id; - const filter = { _id: new ObjectId(id) }; - const result = await Packagecollection.deleteOne(filter); - res.send(result); - }); + app.delete( + "/Package/:id", + authenticateUser, + requireRole(["admin"]), + validateObjectId, + async (req, res) => { + try { + const id = req.params.id; + const filter = { _id: new ObjectId(id) }; + + // Check if package exists + const existingPackage = await Packagecollection.findOne(filter); + if (!existingPackage) { + return res.status(404).json({ + error: "Package not found", + code: "PACKAGE_NOT_FOUND", + }); + } + + const result = await Packagecollection.deleteOne(filter); + res.json({ + success: true, + message: "Package deleted successfully", + deletedCount: result.deletedCount, + }); + } catch (error) { + console.error("Package deletion failed:", error.message); + res.status(500).json({ + error: "Failed to delete package", + code: "DELETION_FAILED", + }); + } + } + ); //To get single package data From 83e93916d5d06e7bca0488a0dea483489986e24d Mon Sep 17 00:00:00 2001 From: HasinduOnline <40927446+hrtechplus@users.noreply.github.com> Date: Tue, 23 Sep 2025 22:49:16 +0530 Subject: [PATCH 3/3] refactor(server): update CORS configuration and improve code formatting across multiple files --- environment-setup.md | 163 ++++++ feedbackapp/backend/routes/feedbackRouter.js | 25 +- feedbackapp/backend/server.js | 43 +- guestapp/backend/index.js | 13 +- middleware/basicAuth.js | 51 ++ .../backend/controllers/serviceController.js | 525 +++++++++--------- .../backend/controllers/vendorController.js | 102 ++-- vendorapp/backend/index.js | 28 +- 8 files changed, 626 insertions(+), 324 deletions(-) create mode 100644 environment-setup.md create mode 100644 middleware/basicAuth.js diff --git a/environment-setup.md b/environment-setup.md new file mode 100644 index 0000000..e337a90 --- /dev/null +++ b/environment-setup.md @@ -0,0 +1,163 @@ +# Environment Configuration Setup + +## Required .env Files + +Create `.env` file in **each backend directory** with these variables: + +### 1. budgetapp/backend/.env + +```bash +NODE_ENV=development +PORT=7000 +MONGODB_URL=mongodb://localhost:27017/wedding_budget +JWT_SECRET_KEY=your-super-secret-jwt-key-minimum-32-characters-budget +SESSION_SECRET=your-super-secret-session-key-minimum-32-characters-budget +``` + +### 2. eventapp/backend/.env + +```bash +NODE_ENV=development +PORT=5003 +MONGO_CONNECTION_STRING=mongodb://localhost:27017/wedding_events +JWT_SECRET_KEY=your-super-secret-jwt-key-minimum-32-characters-events +SESSION_SECRET=your-super-secret-session-key-minimum-32-characters-events +GOOGLE_CLIENT_ID=your-google-oauth-client-id +GOOGLE_CLIENT_SECRET=your-google-oauth-client-secret +GOOGLE_CALLBACK_URL=http://localhost:5003/auth/google/callback +FRONTEND_URL=http://localhost:3003 +``` + +### 3. guestapp/backend/.env + +```bash +NODE_ENV=development +PORT=5002 +MONGO_URL=mongodb://localhost:27017/wedding_guests +JWT_SECRET_KEY=your-super-secret-jwt-key-minimum-32-characters-guests +SESSION_SECRET=your-super-secret-session-key-minimum-32-characters-guests +``` + +### 4. mainapp/backend/.env + +```bash +NODE_ENV=development +PORT=8000 +MONGO_URI=mongodb://localhost:27017/wedding_main +JWT_SECRET_KEY=your-super-secret-jwt-key-minimum-32-characters-main +SESSION_SECRET=your-super-secret-session-key-minimum-32-characters-main +``` + +### 5. packageapp/backend/.env + +```bash +NODE_ENV=development +PORT=5000 +MONGO_URI=mongodb://localhost:27017/wedding_packages +JWT_SECRET_KEY=your-super-secret-jwt-key-minimum-32-characters-packages +SESSION_SECRET=your-super-secret-session-key-minimum-32-characters-packages +``` + +### 6. vendorapp/backend/.env + +```bash +NODE_ENV=development +PORT=5001 +MONGO_URL=mongodb://localhost:27017/wedding_vendors +JWT_SECRET_KEY=your-super-secret-jwt-key-minimum-32-characters-vendors +SESSION_SECRET=your-super-secret-session-key-minimum-32-characters-vendors +SECRET_KEY=your-vendor-specific-secret-key-minimum-32-characters +``` + +### 7. feedbackapp/backend/.env + +```bash +NODE_ENV=development +PORT=3001 +MONGO_URI=mongodb://localhost:27017/wedding_feedback +JWT_SECRET_KEY=your-super-secret-jwt-key-minimum-32-characters-feedback +SESSION_SECRET=your-super-secret-session-key-minimum-32-characters-feedback +ADMIN_PASSWORD=your-admin-password-for-basic-auth +``` + +### 8. taskapp/backend/.env + +```bash +NODE_ENV=development +PORT=8080 +MONGO_URL=mongodb://localhost:27017/wedding_tasks +JWT_SECRET_KEY=your-super-secret-jwt-key-minimum-32-characters-tasks +SESSION_SECRET=your-super-secret-session-key-minimum-32-characters-tasks +FRONTEND_DOMAIN=http://localhost:3000 +GOOGLE_CLIENT_ID=your-google-oauth-client-id +GOOGLE_CLIENT_SECRET=your-google-oauth-client-secret +FACEBOOK_CLIENT_ID=your-facebook-oauth-client-id +FACEBOOK_CLIENT_SECRET=your-facebook-oauth-client-secret +``` + +## Security Requirements + +### Secret Key Generation + +Use this command to generate secure keys: + +```bash +node -e "console.log(require('crypto').randomBytes(32).toString('hex'))" +``` + +### Database Setup + +1. Install MongoDB locally or use MongoDB Atlas +2. Create separate databases for each service +3. Ensure MongoDB authentication is enabled in production + +### OAuth Setup + +1. Create Google OAuth application at: https://console.cloud.google.com/ +2. Create Facebook OAuth application at: https://developers.facebook.com/ +3. Set appropriate callback URLs + +## Installation Script + +Create this script to set up all environments: + +```bash +#!/bin/bash +# setup-env.sh + +# Generate a secure key +generate_key() { + node -e "console.log(require('crypto').randomBytes(32).toString('hex'))" +} + +# Create .env files for each backend +backends=("budgetapp" "eventapp" "guestapp" "mainapp" "packageapp" "vendorapp" "feedbackapp" "taskapp") + +for backend in "${backends[@]}"; do + if [ -d "${backend}/backend" ]; then + echo "Setting up ${backend}/backend/.env" + # Copy template and replace placeholders + # (Implementation depends on your setup) + fi +done + +echo "Environment setup complete!" +echo "Remember to:" +echo "1. Update all placeholder values with actual secrets" +echo "2. Set up OAuth applications" +echo "3. Configure MongoDB connection strings" +echo "4. Never commit .env files to version control" +``` + +## Verification + +Test your setup: + +```bash +# Check each backend can start +cd budgetapp/backend && npm start +cd eventapp/backend && npm start +# ... etc for each backend +``` + +**IMPORTANT**: Never commit `.env` files to version control! diff --git a/feedbackapp/backend/routes/feedbackRouter.js b/feedbackapp/backend/routes/feedbackRouter.js index 6b14c25..b0f553b 100644 --- a/feedbackapp/backend/routes/feedbackRouter.js +++ b/feedbackapp/backend/routes/feedbackRouter.js @@ -1,10 +1,21 @@ -const express = require('express'); +const express = require("express"); const router = express.Router(); -const controller = require('../controllers/feedbackController'); +const controller = require("../controllers/feedbackController"); +const { basicAuth, requireAdmin } = require("../../../middleware/basicAuth.js"); -router.get('/feedbacks',controller.getFeedback); -router.post('/createfeedback',controller.addFeedback); -router.post('/updatefeedback',controller.updateFeedback); -router.post('/deletefeedback',controller.deleteFeedback); +router.get("/feedbacks", controller.getFeedback); +router.post("/createfeedback", basicAuth, controller.addFeedback); +router.post( + "/updatefeedback", + basicAuth, + requireAdmin, + controller.updateFeedback +); +router.post( + "/deletefeedback", + basicAuth, + requireAdmin, + controller.deleteFeedback +); -module.exports = router; \ No newline at end of file +module.exports = router; diff --git a/feedbackapp/backend/server.js b/feedbackapp/backend/server.js index 62afbcb..73186d3 100644 --- a/feedbackapp/backend/server.js +++ b/feedbackapp/backend/server.js @@ -1,33 +1,44 @@ -const express = require('express'); +const express = require("express"); const app = express(); -const cors = require('cors'); -const dotenv = require('dotenv'); +const cors = require("cors"); +const dotenv = require("dotenv"); dotenv.config(); const port = 3001; -const host = 'localhost'; -const mongoose = require('mongoose'); -const router = require('./routes/feedbackRouter'); +const host = "localhost"; +const mongoose = require("mongoose"); +const router = require("./routes/feedbackRouter"); -app.use(cors()); //cors origin unblocking(cross origine resoures sharing) +app.use( + cors({ + origin: [ + "http://localhost:3000", + "http://localhost:3001", + "http://localhost:3004", + ], + credentials: true, + methods: ["GET", "POST", "PUT", "DELETE", "PATCH"], + allowedHeaders: ["Content-Type", "Authorization"], + }) +); //cors origin blocking for security app.use(express.json()); // Use environment variable for MongoDB URI const uri = process.env.MONGO_URI; const connect = async () => { - try { - await mongoose.connect(uri); - console.log('connected to mongoDB'); - } catch (error) { - console.log('mongoDB error: ',error); - } + try { + await mongoose.connect(uri); + console.log("connected to mongoDB"); + } catch (error) { + console.log("mongoDB error: ", error); + } }; connect(); //call back function -const server = app.listen(port,host, () => { - console.log(`Node server is listing to ${server.address().port}`) +const server = app.listen(port, host, () => { + console.log(`Node server is listing to ${server.address().port}`); }); -app.use('/api',router); \ No newline at end of file +app.use("/api", router); diff --git a/guestapp/backend/index.js b/guestapp/backend/index.js index b502185..0316385 100644 --- a/guestapp/backend/index.js +++ b/guestapp/backend/index.js @@ -14,7 +14,18 @@ dotenv.config(); // app.use(bodyParser.urlencoded({ limit: '10mb', extended: true })) app.use(express.json({ limit: "10mb" })); -app.use(cors()); +app.use( + cors({ + origin: [ + "http://localhost:3000", + "http://localhost:3001", + "http://localhost:3003", + ], + credentials: true, + methods: ["GET", "POST", "PUT", "DELETE", "PATCH"], + allowedHeaders: ["Content-Type", "Authorization"], + }) +); mongoose .connect(process.env.MONGO_URL, { diff --git a/middleware/basicAuth.js b/middleware/basicAuth.js new file mode 100644 index 0000000..14b1797 --- /dev/null +++ b/middleware/basicAuth.js @@ -0,0 +1,51 @@ +// Basic authentication middleware for applications without full user management +const basicAuth = (req, res, next) => { + const authHeader = req.headers.authorization; + + if (!authHeader || !authHeader.startsWith("Basic ")) { + return res.status(401).json({ + error: "Authentication required", + code: "NO_AUTH", + }); + } + + try { + const base64Credentials = authHeader.substring(6); + const credentials = Buffer.from(base64Credentials, "base64").toString( + "ascii" + ); + const [username, password] = credentials.split(":"); + + // Simple admin check (in production, use proper user management) + if (username === "admin" && password === process.env.ADMIN_PASSWORD) { + req.user = { role: "admin", username: "admin" }; + next(); + } else { + return res.status(401).json({ + error: "Invalid credentials", + code: "INVALID_CREDENTIALS", + }); + } + } catch (error) { + return res.status(401).json({ + error: "Invalid authorization header", + code: "INVALID_AUTH_HEADER", + }); + } +}; + +// Role-based authorization +const requireAdmin = (req, res, next) => { + if (!req.user || req.user.role !== "admin") { + return res.status(403).json({ + error: "Admin access required", + code: "ADMIN_REQUIRED", + }); + } + next(); +}; + +module.exports = { + basicAuth, + requireAdmin, +}; diff --git a/vendorapp/backend/controllers/serviceController.js b/vendorapp/backend/controllers/serviceController.js index d3c2638..b8811a2 100644 --- a/vendorapp/backend/controllers/serviceController.js +++ b/vendorapp/backend/controllers/serviceController.js @@ -2,325 +2,350 @@ const Service = require("../models/serviceSchema"); const Couple = require("../models/coupleSchema"); const serviceCreate = async (req, res) => { - try { - const service = new Service(req.body) + try { + const service = new Service(req.body); - let result = await service.save(); + let result = await service.save(); - res.send(result); - } catch (err) { - res.status(500).json(err); - } + res.send(result); + } catch (err) { + res.status(500).json(err); + } }; const getServices = async (req, res) => { - try { - let services = await Service.find().populate("vendor", "shopName"); - if (services.length > 0) { - res.send(services); - } else { - res.send({ message: "No services found" }); - } - } catch (err) { - res.status(500).json(err); + try { + let services = await Service.find().populate("vendor", "shopName"); + if (services.length > 0) { + res.send(services); + } else { + res.send({ message: "No services found" }); } + } catch (err) { + res.status(500).json(err); + } }; const getVendorServices = async (req, res) => { - try { - let services = await Service.find({ vendor: req.params.id }) - if (services.length > 0) { - res.send(services) - } else { - res.send({ message: "No services found" }); - } - } catch (err) { - res.status(500).json(err); + try { + // Validate ObjectId to prevent NoSQL injection + if (!require("mongoose").Types.ObjectId.isValid(req.params.id)) { + return res.status(400).json({ error: "Invalid vendor ID format" }); } + let services = await Service.find({ + vendor: require("mongoose").Types.ObjectId(req.params.id), + }); + if (services.length > 0) { + res.send(services); + } else { + res.send({ message: "No services found" }); + } + } catch (err) { + res.status(500).json(err); + } }; const getServiceDetail = async (req, res) => { - try { - let service = await Service.findById(req.params.id) - .populate("vendor", "shopName") - .populate({ - path: "reviews.reviewer", - model: "couple", - select: "name" - }); - - if (service) { - res.send(service); - } - else { - res.send({ message: "No service found" }); - } - } catch (err) { - res.status(500).json(err); + try { + let service = await Service.findById(req.params.id) + .populate("vendor", "shopName") + .populate({ + path: "reviews.reviewer", + model: "couple", + select: "name", + }); + + if (service) { + res.send(service); + } else { + res.send({ message: "No service found" }); } -} + } catch (err) { + res.status(500).json(err); + } +}; const updateService = async (req, res) => { - try { - let result = await Service.findByIdAndUpdate(req.params.id, - { $set: req.body }, - { new: true }) - - res.send(result) - } catch (error) { - res.status(500).json(error); - } -} + try { + let result = await Service.findByIdAndUpdate( + req.params.id, + { $set: req.body }, + { new: true } + ); + + res.send(result); + } catch (error) { + res.status(500).json(error); + } +}; const addReview = async (req, res) => { - try { - const { rating, comment, reviewer } = req.body; - const serviceId = req.params.id; + try { + const { rating, comment, reviewer } = req.body; + const serviceId = req.params.id; - const service = await Service.findById(serviceId); + const service = await Service.findById(serviceId); - const existingReview = service.reviews.find(review => review.reviewer.toString() === reviewer); + const existingReview = service.reviews.find( + (review) => review.reviewer.toString() === reviewer + ); - if (existingReview) { - return res.send({ message: "You have already submitted a review for this service." }); - } + if (existingReview) { + return res.send({ + message: "You have already submitted a review for this service.", + }); + } - service.reviews.push({ - rating, - comment, - reviewer, - date: new Date(), - }); + service.reviews.push({ + rating, + comment, + reviewer, + date: new Date(), + }); - const updatedService = await service.save(); + const updatedService = await service.save(); - res.send(updatedService); - } catch (error) { - res.status(500).json(error); - } + res.send(updatedService); + } catch (error) { + res.status(500).json(error); + } }; const searchService = async (req, res) => { - try { - const key = req.params.key; - - let services = await Service.find({ - $or: [ - { serviceName: { $regex: key, $options: 'i' } }, - { category: { $regex: key, $options: 'i' } }, - { subcategory: { $regex: key, $options: 'i' } } - ] - }).populate("vendor", "shopName"); - - if (services.length > 0) { - res.send(services); - } else { - res.send({ message: "No services found" }); - } - } catch (err) { - res.status(500).json(err); + try { + const key = req.params.key; + + let services = await Service.find({ + $or: [ + { serviceName: { $regex: key, $options: "i" } }, + { category: { $regex: key, $options: "i" } }, + { subcategory: { $regex: key, $options: "i" } }, + ], + }).populate("vendor", "shopName"); + + if (services.length > 0) { + res.send(services); + } else { + res.send({ message: "No services found" }); } + } catch (err) { + res.status(500).json(err); + } }; const searchServicebyCategory = async (req, res) => { - try { - const key = req.params.key; - - let services = await Service.find({ - $or: [ - { category: { $regex: key, $options: 'i' } }, - ] - }).populate("vendor", "shopName"); - - if (services.length > 0) { - res.send(services); - } else { - res.send({ message: "No services found" }); - } - } catch (err) { - res.status(500).json(err); + try { + const key = req.params.key; + + let services = await Service.find({ + $or: [{ category: { $regex: key, $options: "i" } }], + }).populate("vendor", "shopName"); + + if (services.length > 0) { + res.send(services); + } else { + res.send({ message: "No services found" }); } + } catch (err) { + res.status(500).json(err); + } }; const searchServicebySubCategory = async (req, res) => { - try { - const key = req.params.key; - - let services = await Service.find({ - $or: [ - { subcategory: { $regex: key, $options: 'i' } } - ] - }).populate("vendor", "shopName"); - - if (services.length > 0) { - res.send(services); - } else { - res.send({ message: "No services found" }); - } - } catch (err) { - res.status(500).json(err); + try { + const key = req.params.key; + + let services = await Service.find({ + $or: [{ subcategory: { $regex: key, $options: "i" } }], + }).populate("vendor", "shopName"); + + if (services.length > 0) { + res.send(services); + } else { + res.send({ message: "No services found" }); } + } catch (err) { + res.status(500).json(err); + } }; const deleteService = async (req, res) => { - try { - const deletedService = await Service.findByIdAndDelete(req.params.id); - - await Couple.updateMany( - { "invoiceDetails._id": deletedService._id }, - { $pull: { invoiceDetails: { _id: deletedService._id } } } - ); - - res.send(deletedService); - } catch (error) { - res.status(500).json(error); - } + try { + const deletedService = await Service.findByIdAndDelete(req.params.id); + + await Couple.updateMany( + { "invoiceDetails._id": deletedService._id }, + { $pull: { invoiceDetails: { _id: deletedService._id } } } + ); + + res.send(deletedService); + } catch (error) { + res.status(500).json(error); + } }; const deleteServices = async (req, res) => { - try { - const deletionResult = await Service.deleteMany({ vendor: req.params.id }); + try { + const deletionResult = await Service.deleteMany({ vendor: req.params.id }); - const deletedCount = deletionResult.deletedCount || 0; - - if (deletedCount === 0) { - res.send({ message: "No services found to delete" }); - return; - } - - const deletedServices = await Service.find({ vendor: req.params.id }); - - await Couple.updateMany( - { "invoiceDetails._id": { $in: deletedServices.map(service => service._id) } }, - { $pull: { invoiceDetails: { _id: { $in: deletedServices.map(service => service._id) } } } } - ); + const deletedCount = deletionResult.deletedCount || 0; - res.send(deletionResult); - } catch (error) { - res.status(500).json(error); + if (deletedCount === 0) { + res.send({ message: "No services found to delete" }); + return; } -}; + const deletedServices = await Service.find({ vendor: req.params.id }); + + await Couple.updateMany( + { + "invoiceDetails._id": { + $in: deletedServices.map((service) => service._id), + }, + }, + { + $pull: { + invoiceDetails: { + _id: { $in: deletedServices.map((service) => service._id) }, + }, + }, + } + ); + + res.send(deletionResult); + } catch (error) { + res.status(500).json(error); + } +}; const deleteServiceReview = async (req, res) => { - try { - const { reviewId } = req.body; - const serviceId = req.params.id; + try { + const { reviewId } = req.body; + const serviceId = req.params.id; - const service = await Service.findById(serviceId); + const service = await Service.findById(serviceId); - const updatedReviews = service.reviews.filter(review => review._id != reviewId); + const updatedReviews = service.reviews.filter( + (review) => review._id != reviewId + ); - service.reviews = updatedReviews; + service.reviews = updatedReviews; - const updatedService = await service.save(); + const updatedService = await service.save(); - res.send(updatedService); - } catch (error) { - res.status(500).json(error); - } + res.send(updatedService); + } catch (error) { + res.status(500).json(error); + } }; const deleteAllServiceReviews = async (req, res) => { - try { - const service = await Service.findById(req.params.id); - service.reviews = []; + try { + const service = await Service.findById(req.params.id); + service.reviews = []; - const updatedService = await service.save(); + const updatedService = await service.save(); - res.send(updatedService); - } catch (error) { - res.status(500).json(error); - } + res.send(updatedService); + } catch (error) { + res.status(500).json(error); + } }; const getInterestedCouples = async (req, res) => { - try { - const serviceId = req.params.id; - - const interestedCouples = await Couple.find({ - 'invoiceDetails._id': serviceId - }); - - const coupleDetails = interestedCouples.map(couple => { - const invoiceItem = couple.invoiceDetails.find(item => item._id.toString() === serviceId); - if (invoiceItem) { - return { - coupleName: couple.name, - coupleID: couple._id, - quantity: invoiceItem.quantity, - }; - } - return null; // If invoiceItem is not found in this couple's invoiceDetails - }).filter(item => item !== null); // Remove null values from the result - - if (coupleDetails.length > 0) { - res.send(coupleDetails); - } else { - res.send({ message: 'No couples are interested in this service.' }); + try { + const serviceId = req.params.id; + + const interestedCouples = await Couple.find({ + "invoiceDetails._id": serviceId, + }); + + const coupleDetails = interestedCouples + .map((couple) => { + const invoiceItem = couple.invoiceDetails.find( + (item) => item._id.toString() === serviceId + ); + if (invoiceItem) { + return { + coupleName: couple.name, + coupleID: couple._id, + quantity: invoiceItem.quantity, + }; } - } catch (error) { - res.status(500).json(error); + return null; // If invoiceItem is not found in this couple's invoiceDetails + }) + .filter((item) => item !== null); // Remove null values from the result + + if (coupleDetails.length > 0) { + res.send(coupleDetails); + } else { + res.send({ message: "No couples are interested in this service." }); } + } catch (error) { + res.status(500).json(error); + } }; const getAddedToInvoiceServices = async (req, res) => { - try { - const vendorId = req.params.id; - - const couplesWithVendorService = await Couple.find({ - 'invoiceDetails.vendor': vendorId - }); - - const serviceMap = new Map(); // Use a Map to aggregate services by ID - couplesWithVendorService.forEach(couple => { - couple.invoiceDetails.forEach(invoiceItem => { - if (invoiceItem.vendor.toString() === vendorId) { - const serviceId = invoiceItem._id.toString(); - if (serviceMap.has(serviceId)) { - // If service ID already exists, update the quantity - const existingService = serviceMap.get(serviceId); - existingService.quantity += invoiceItem.quantity; - } else { - // If service ID does not exist, add it to the Map - serviceMap.set(serviceId, { - serviceName: invoiceItem.serviceName, - quantity: invoiceItem.quantity, - category: invoiceItem.category, - subcategory: invoiceItem.subcategory, - serviceID: serviceId, - }); - } - } + try { + const vendorId = req.params.id; + + const couplesWithVendorService = await Couple.find({ + "invoiceDetails.vendor": vendorId, + }); + + const serviceMap = new Map(); // Use a Map to aggregate services by ID + couplesWithVendorService.forEach((couple) => { + couple.invoiceDetails.forEach((invoiceItem) => { + if (invoiceItem.vendor.toString() === vendorId) { + const serviceId = invoiceItem._id.toString(); + if (serviceMap.has(serviceId)) { + // If service ID already exists, update the quantity + const existingService = serviceMap.get(serviceId); + existingService.quantity += invoiceItem.quantity; + } else { + // If service ID does not exist, add it to the Map + serviceMap.set(serviceId, { + serviceName: invoiceItem.serviceName, + quantity: invoiceItem.quantity, + category: invoiceItem.category, + subcategory: invoiceItem.subcategory, + serviceID: serviceId, }); - }); - - const servicesInInvoice = Array.from(serviceMap.values()); - - if (servicesInInvoice.length > 0) { - res.send(servicesInInvoice); - } else { - res.send({ message: 'No services from this vendor are added to invoice by couples.' }); + } } - } catch (error) { - res.status(500).json(error); + }); + }); + + const servicesInInvoice = Array.from(serviceMap.values()); + + if (servicesInInvoice.length > 0) { + res.send(servicesInInvoice); + } else { + res.send({ + message: + "No services from this vendor are added to invoice by couples.", + }); } + } catch (error) { + res.status(500).json(error); + } }; module.exports = { - serviceCreate, - getServices, - getVendorServices, - getServiceDetail, - updateService, - addReview, - searchService, - searchServicebyCategory, - searchServicebySubCategory, - deleteService, - deleteServices, - deleteServiceReview, - deleteAllServiceReviews, - getInterestedCouples, - getAddedToInvoiceServices, -}; \ No newline at end of file + serviceCreate, + getServices, + getVendorServices, + getServiceDetail, + updateService, + addReview, + searchService, + searchServicebyCategory, + searchServicebySubCategory, + deleteService, + deleteServices, + deleteServiceReview, + deleteAllServiceReviews, + getInterestedCouples, + getAddedToInvoiceServices, +}; diff --git a/vendorapp/backend/controllers/vendorController.js b/vendorapp/backend/controllers/vendorController.js index 71655c8..bc1a73e 100644 --- a/vendorapp/backend/controllers/vendorController.js +++ b/vendorapp/backend/controllers/vendorController.js @@ -1,69 +1,73 @@ -const bcrypt = require('bcrypt'); -const Vendor = require('../models/vendorSchema.js'); -const { createNewToken } = require('../utils/token.js'); +const bcrypt = require("bcrypt"); +const Vendor = require("../models/vendorSchema.js"); +const { createNewToken } = require("../utils/token.js"); +const mongoose = require("mongoose"); const vendorRegister = async (req, res) => { - try { - const salt = await bcrypt.genSalt(10); - const hashedPass = await bcrypt.hash(req.body.password, salt); + try { + const salt = await bcrypt.genSalt(10); + const hashedPass = await bcrypt.hash(req.body.password, salt); - const vendor = new Vendor({ - ...req.body, - password: hashedPass - }); + const vendor = new Vendor({ + ...req.body, + password: hashedPass, + }); - const existingVendorByEmail = await Vendor.findOne({ email: req.body.email }); - const existingShop = await Vendor.findOne({ shopName: req.body.shopName }); + const existingVendorByEmail = await Vendor.findOne({ + email: req.body.email, + }); + const existingShop = await Vendor.findOne({ shopName: req.body.shopName }); - if (existingVendorByEmail) { - res.send({ message: 'Email already exists' }); - } - else if (existingShop) { - res.send({ message: 'Shop name already exists' }); - } - else { - let result = await vendor.save(); - result.password = undefined; + if (existingVendorByEmail) { + res.send({ message: "Email already exists" }); + } else if (existingShop) { + res.send({ message: "Shop name already exists" }); + } else { + let result = await vendor.save(); + result.password = undefined; - const token = createNewToken(result._id) + const token = createNewToken(result._id); - result = { - ...result._doc, - token: token - }; + result = { + ...result._doc, + token: token, + }; - res.send(result); - } - } catch (err) { - res.status(500).json(err); + res.send(result); } + } catch (err) { + res.status(500).json(err); + } }; const vendorLogIn = async (req, res) => { - if (req.body.email && req.body.password) { - let vendor = await Vendor.findOne({ email: req.body.email }); - if (vendor) { - const validated = await bcrypt.compare(req.body.password, vendor.password); - if (validated) { - vendor.password = undefined; + if (req.body.email && req.body.password) { + let vendor = await Vendor.findOne({ email: req.body.email }); + if (vendor) { + const validated = await bcrypt.compare( + req.body.password, + vendor.password + ); + if (validated) { + vendor.password = undefined; - const token = createNewToken(vendor._id) + const token = createNewToken(vendor._id); - vendor = { - ...vendor._doc, - token: token - }; + vendor = { + ...vendor._doc, + token: token, + }; - res.send(vendor); - } else { - res.send({ message: "Invalid password" }); - } - } else { - res.send({ message: "User not found" }); - } + res.send(vendor); + } else { + res.send({ message: "Invalid password" }); + } } else { - res.send({ message: "Email and password are required" }); + res.send({ message: "User not found" }); } + } else { + res.send({ message: "Email and password are required" }); + } }; module.exports = { vendorRegister, vendorLogIn }; diff --git a/vendorapp/backend/index.js b/vendorapp/backend/index.js index 88ede6f..51c879a 100644 --- a/vendorapp/backend/index.js +++ b/vendorapp/backend/index.js @@ -2,6 +2,7 @@ const express = require("express"); const cors = require("cors"); const mongoose = require("mongoose"); const dotenv = require("dotenv"); +const helmet = require("helmet"); const app = express(); const Routes = require("./routes/route.js"); @@ -10,8 +11,33 @@ const PORT = process.env.PORT || 5001; dotenv.config(); +// Security headers +app.use( + helmet({ + contentSecurityPolicy: { + directives: { + defaultSrc: ["'self'"], + styleSrc: ["'self'", "'unsafe-inline'"], + scriptSrc: ["'self'"], + imgSrc: ["'self'", "data:", "https:"], + }, + }, + }) +); + app.use(express.json({ limit: "10mb" })); -app.use(cors()); +app.use( + cors({ + origin: [ + "http://localhost:3000", + "http://localhost:3001", + "http://localhost:3002", + ], + credentials: true, + methods: ["GET", "POST", "PUT", "DELETE", "PATCH"], + allowedHeaders: ["Content-Type", "Authorization"], + }) +); mongoose .connect(process.env.MONGO_URL, {