From 33780616c3b1752789a1a56a38f9c0a0675a4cd7 Mon Sep 17 00:00:00 2001 From: Gowtham M Date: Thu, 4 Dec 2025 11:06:27 +0530 Subject: [PATCH] GWM : login logic changed --- app/controllers/auth.controller.js | 69 ++++-- app/controllers/establishment.controller.js | 29 +++ .../notificationTemplate.controller.js | 104 ++++++--- app/middleware/auth.middleware.js | 44 +++- app/routes/routes.js | 8 +- app/utils/sanitize.js | 12 ++ package-lock.json | 201 ++++++++++++++++++ package.json | 2 + server.js | 56 ++++- 9 files changed, 461 insertions(+), 64 deletions(-) create mode 100644 app/utils/sanitize.js diff --git a/app/controllers/auth.controller.js b/app/controllers/auth.controller.js index e061c61..50fdc24 100644 --- a/app/controllers/auth.controller.js +++ b/app/controllers/auth.controller.js @@ -7,24 +7,6 @@ const User = db.user; const EstablishmentUser = db.EstablishmentUser; const Establishment = db.Establishment; -// Register new user -exports.register = async (req, res) => { - try { - const { name, email, password } = req.body; - if (!name || !email || !password) - return res.status(400).send({'status':"failed",'message':"All fields required",'data': ""}); - - const existing = await User.findOne({ where: { email } }); - if (existing) res.status(400).send({'status':"failed",'message':"Email already used",'data': ""}); - - const hashedPassword = await bcrypt.hash(password, 10); - const newUser = await User.create({ name, email, password: hashedPassword }); - - res.status(201).send({'status':"success",'message':"User registered successfully",'data': newUser }); - } catch (err) { - res.status(500).send({'status':"failed",'message':err.message }); - } -}; //Admin user and Establishment user login exports.login = async (req, res) => { @@ -91,7 +73,22 @@ exports.login = async (req, res) => { expiresIn: "6h", }); - return res.status(200).json({ status: "success", message: "Login successful", data: token }); + // Set token in HTTP-only cookie (IMPORTANT PART) + res.cookie("auth_token", token, { + httpOnly: true, + secure: process.env.NODE_ENV === "production", // true in prod (HTTPS) + sameSite: "lax", // or "strict" if suitable + maxAge: 6 * 60 * 60 * 1000, // 6 hours in ms + }); + + // Optionally return minimal user info (WITHOUT password) + return res.status(200).json({ + status: "success", + message: "Login successful", + data: tokenData, + }); + + // return res.status(200).json({ status: "success", message: "Login successful", data: token }); } catch (err) { return res.status(500).json({ @@ -101,6 +98,40 @@ exports.login = async (req, res) => { } }; +//logout +exports.logout = (req, res) => { + res.clearCookie("auth_token", { + httpOnly: true, + secure: process.env.NODE_ENV === "production", + sameSite: "lax", + }); + + return res.status(200).json({ + status: "success", + message: "Logged out successfully", + }); +}; + + +// Register new user +exports.register = async (req, res) => { + try { + const { name, email, password } = req.body; + if (!name || !email || !password) + return res.status(400).send({'status':"failed",'message':"All fields required",'data': ""}); + + const existing = await User.findOne({ where: { email } }); + if (existing) res.status(400).send({'status':"failed",'message':"Email already used",'data': ""}); + + const hashedPassword = await bcrypt.hash(password, 10); + const newUser = await User.create({ name, email, password: hashedPassword }); + + res.status(201).send({'status':"success",'message':"User registered successfully",'data': newUser }); + } catch (err) { + res.status(500).send({'status':"failed",'message':err.message }); + } +}; + // Login user // exports.login = async (req, res) => { // try { diff --git a/app/controllers/establishment.controller.js b/app/controllers/establishment.controller.js index fe16f07..28f1bf5 100644 --- a/app/controllers/establishment.controller.js +++ b/app/controllers/establishment.controller.js @@ -34,6 +34,20 @@ exports.testEmail = async (req, res) => { exports.createEstablishment = async (req, res) => { try { + + // convert empty "" → null BEFORE destructure + [ + "establishment_code", + "factory_name", + "permanent_factory_code", + "industry_code", + "industry_code_production", + "license_number", + "isic_code" + ].forEach(key => { + if (req.body[key] === "") req.body[key] = null; + }); + const { establishment_code, factory_name, @@ -81,6 +95,7 @@ exports.createEstablishment = async (req, res) => { establishment_products } = req.body; + // Basic Validation if (!establishment_code || !factory_name || !establishment_user) { return res.status(400).send({ @@ -585,6 +600,20 @@ exports.getEstablishmentById = async (req, res) => { // Update establishment exports.updateEstablishment = async (req, res) => { try { + + // convert empty "" → null BEFORE destructure + [ + "establishment_code", + "factory_name", + "permanent_factory_code", + "industry_code", + "industry_code_production", + "license_number", + "isic_code" + ].forEach(key => { + if (req.body[key] === "") req.body[key] = null; + }); + const { id } = req.params; const { establishment_products, establishment_user, ...estData } = req.body; const user = await EstablishmentUser.findOne({where:{establishment_id:id}}); diff --git a/app/controllers/notificationTemplate.controller.js b/app/controllers/notificationTemplate.controller.js index 118493f..b262943 100644 --- a/app/controllers/notificationTemplate.controller.js +++ b/app/controllers/notificationTemplate.controller.js @@ -1,18 +1,87 @@ const db = require("../models"); const NotificationTemplate = db.NotificationTemplate; +const { sanitizeHtml } = require("../utils/sanitize"); // Create template exports.createTemplate = async (req, res) => { - try { - req.body.created_by = req.body.created_by || req.user.id; - const template = await NotificationTemplate.create(req.body); - res.status(201).send({ + + try { + const { title , template_code, subject, body_html , created_by } = req.body; + + if (!title || !template_code || !subject || !body_html ) { + return res.status(400).json({ + status: "failed", + message: "Missing required fields", + }); + } + + // sanitize input + const cleanHtml = sanitizeHtml(body_html); + + const template = await NotificationTemplate.create({ + title, + template_code, + subject, + body_html: cleanHtml, + created_by: created_by || req.user?.id || null, + }); + + return res.status(201).json({ status: "success", message: "Template created successfully", - data: template, + data: { + id: template.id, + template_code: template.template_code, + subject: template.subject + // DO NOT return HTML to avoid XSS + }, }); + } catch (err) { - res.status(500).send({ status: "failed", message: err.message }); + res.status(500).json({ status: "failed", message: err.message }); + } +}; + +// Update template +exports.updateTemplate = async (req, res) => { + try { + const id = req.params.id; + + // whitelist fields + const updateData = {}; + if (req.body.title) updateData.title = req.body.title; + if (req.body.subject) updateData.subject = req.body.subject; + + // sanitize HTML if provided + if (req.body.body_html) { + updateData.body_html = sanitizeHtml(req.body.body_html); + } + + updateData.updated_by = req.body.updated_by || req.user?.id || null; + updateData.updated_at = new Date(); + + // run update + const [updated] = await NotificationTemplate.update(updateData, { + where: { id } + }); + + if (!updated) { + return res.status(404).json({ + status: "failed", + message: "Template not found" + }); + } + + return res.status(200).json({ + status: "success", + message: "Template updated successfully" + }); + + } catch (err) { + return res.status(500).json({ + status: "failed", + message: err.message + }); } }; @@ -49,29 +118,6 @@ exports.getTemplateById = async (req, res) => { } }; -// Update template -exports.updateTemplate = async (req, res) => { - try { - req.body.updated_by = req.body.updated_by || req.user.id; - req.body.updated_at = req.body.updated_at || new Date(); - - const [updated] = await NotificationTemplate.update(req.body, { - where: { id: req.params.id }, - }); - - if (!updated) - return res - .status(404) - .send({ status: "failed", message: "Template not found" }); - - res - .status(200) - .send({ status: "success", message: "Updated successfully" }); - } catch (err) { - res.status(500).send({ status: "failed", message: err.message }); - } -}; - // Delete template exports.deleteTemplate = async (req, res) => { try { diff --git a/app/middleware/auth.middleware.js b/app/middleware/auth.middleware.js index 02ef505..1e76944 100644 --- a/app/middleware/auth.middleware.js +++ b/app/middleware/auth.middleware.js @@ -1,15 +1,45 @@ const jwt = require("jsonwebtoken"); require("dotenv").config(); -module.exports = function (req, res, next) { - const authHeader = req.headers["authorization"]; - const token = authHeader && authHeader.split(" ")[1]; // Expect "Bearer " +// module.exports = function (req, res, next) { +// const authHeader = req.headers["authorization"]; +// const token = authHeader && authHeader.split(" ")[1]; // Expect "Bearer " - if (!token) return res.status(403).json({ message: "Access denied, token missing" }); +// if (!token) return res.status(403).json({ message: "Access denied, token missing" }); - jwt.verify(token, process.env.JWT_SECRET, (err, decoded) => { - if (err) return res.status(401).json({ message: "Invalid or expired token" }); +// jwt.verify(token, process.env.JWT_SECRET, (err, decoded) => { +// if (err) return res.status(401).json({ message: "Invalid or expired token" }); +// req.user = decoded; +// next(); +// }); +// }; + + +const authJWT = (req, res, next) => { + try { + const token = + req.cookies?.auth_token || + (req.headers.authorization && req.headers.authorization.split(" ")[1]); + + if (!token) { + return res + .status(401) + .json({ status: "failed", message: "Unauthorized: No token provided" }); + } + + console.log('token ='+token) + + const decoded = jwt.verify(token, process.env.JWT_SECRET); + + // Attach user info to req for downstream handlers req.user = decoded; next(); - }); + } catch (err) { + return res + .status(401) + .json({ status: "failed", message: "Unauthorized: Invalid token" }); + } }; + +module.exports = authJWT; + diff --git a/app/routes/routes.js b/app/routes/routes.js index 511c9bd..8f7a501 100644 --- a/app/routes/routes.js +++ b/app/routes/routes.js @@ -69,6 +69,10 @@ const upload = multer({ dest: "../writable/uploads/products_bulk_uploads_files" + + + + /** * @swagger * components: @@ -201,7 +205,7 @@ router.post("/auth/login",[verifySignature], authController.login); * tags: [Admin Users] * security: * - appSignature: [] - * - bearerAuth: [] + * cookieAuth: [] # or bearerAuth: [] if you use Authorization header * responses: * 201: * description: Fetched successfully @@ -210,7 +214,7 @@ router.post("/auth/login",[verifySignature], authController.login); * 500: * description: Server error */ -router.get("/admin_users", [verifySignature, verifyToken], adminUserController.getAllUsers); +router.get("/admin_users", [verifySignature , verifyToken], adminUserController.getAllUsers); /** * @swagger diff --git a/app/utils/sanitize.js b/app/utils/sanitize.js new file mode 100644 index 0000000..baa0d77 --- /dev/null +++ b/app/utils/sanitize.js @@ -0,0 +1,12 @@ +const sanitize = require("sanitize-html"); + +exports.sanitizeHtml = (html = "") => { + return sanitize(html, { + allowedTags: sanitize.defaults.allowedTags.concat(["img"]), + allowedAttributes: { + "*": ["style", "class"], + img: ["src", "alt"] + }, + disallowedTagsMode: "discard" + }); +}; diff --git a/package-lock.json b/package-lock.json index 29a980c..3f081dd 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,6 +10,7 @@ "license": "ISC", "dependencies": { "bcryptjs": "^3.0.2", + "cookie-parser": "^1.4.7", "cors": "^2.8.5", "csv-parser": "^3.2.0", "dotenv": "^17.2.3", @@ -23,6 +24,7 @@ "mysql2": "^3.15.2", "nodemailer": "^7.0.10", "nodemon": "^3.1.10", + "sanitize-html": "^2.17.0", "sequelize": "^6.37.7", "swagger-jsdoc": "^6.2.8", "swagger-ui-express": "^5.0.1", @@ -677,6 +679,23 @@ "node": ">= 0.6" } }, + "node_modules/cookie-parser": { + "version": "1.4.7", + "resolved": "https://registry.npmjs.org/cookie-parser/-/cookie-parser-1.4.7.tgz", + "integrity": "sha512-nGUvgXnotP3BsjiLX2ypbQnWoGUPIIfHQNZkkC668ntrzGWEZVW70HDEB1qnNGMicPje6EttlIgzo51YSwNQGw==", + "dependencies": { + "cookie": "0.7.2", + "cookie-signature": "1.0.6" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/cookie-parser/node_modules/cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==" + }, "node_modules/cookie-signature": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz", @@ -757,6 +776,14 @@ } } }, + "node_modules/deepmerge": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", + "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/denque": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/denque/-/denque-2.1.0.tgz", @@ -784,6 +811,57 @@ "node": ">=6.0.0" } }, + "node_modules/dom-serializer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", + "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==", + "dependencies": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.2", + "entities": "^4.2.0" + }, + "funding": { + "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" + } + }, + "node_modules/domelementtype": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", + "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ] + }, + "node_modules/domhandler": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", + "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", + "dependencies": { + "domelementtype": "^2.3.0" + }, + "engines": { + "node": ">= 4" + }, + "funding": { + "url": "https://github.com/fb55/domhandler?sponsor=1" + } + }, + "node_modules/domutils": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.2.2.tgz", + "integrity": "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==", + "dependencies": { + "dom-serializer": "^2.0.0", + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3" + }, + "funding": { + "url": "https://github.com/fb55/domutils?sponsor=1" + } + }, "node_modules/dotenv": { "version": "17.2.3", "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.2.3.tgz", @@ -882,6 +960,17 @@ "once": "^1.4.0" } }, + "node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, "node_modules/es-define-property": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", @@ -914,6 +1003,17 @@ "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==" }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/esutils": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", @@ -1249,6 +1349,24 @@ "node": ">=18.0.0" } }, + "node_modules/htmlparser2": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-8.0.2.tgz", + "integrity": "sha512-GYdjWKDkbRLkZ5geuHs5NY1puJ+PXwP7+fHPRz06Eirsb9ugf6d8kkXav6ADhcODhFFPMIXyxkxSuMf3D6NCFA==", + "funding": [ + "https://github.com/fb55/htmlparser2?sponsor=1", + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "dependencies": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3", + "domutils": "^3.0.1", + "entities": "^4.4.0" + } + }, "node_modules/http-errors": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", @@ -1381,6 +1499,14 @@ "node": ">=0.12.0" } }, + "node_modules/is-plain-object": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz", + "integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/is-promise": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz", @@ -1942,6 +2068,23 @@ "node": ">=12.0.0" } }, + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, "node_modules/negotiator": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", @@ -2060,6 +2203,11 @@ "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==" }, + "node_modules/parse-srcset": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/parse-srcset/-/parse-srcset-1.0.2.tgz", + "integrity": "sha512-/2qh0lav6CmI15FzA3i/2Bzk2zCgQhGMkvhOhKNcBVQ1ldgpbfiNTVslmooUmWJcADi1f1kIeynbDRVzNlfR6Q==" + }, "node_modules/parseurl": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", @@ -2090,6 +2238,11 @@ "resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.9.1.tgz", "integrity": "sha512-nkc6NpDcvPVpZXxrreI/FOtX3XemeLl8E0qFr6F2Lrm/I8WOnaWNhIPK2Z7OHpw7gh5XJThi6j6ppgNoaT1w4w==" }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==" + }, "node_modules/picomatch": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", @@ -2101,6 +2254,33 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/postcss": { + "version": "8.5.6", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", + "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, "node_modules/process-nextick-args": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", @@ -2289,6 +2469,19 @@ "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" }, + "node_modules/sanitize-html": { + "version": "2.17.0", + "resolved": "https://registry.npmjs.org/sanitize-html/-/sanitize-html-2.17.0.tgz", + "integrity": "sha512-dLAADUSS8rBwhaevT12yCezvioCA+bmUTPH/u57xKPT8d++voeYE6HeluA/bPbQ15TwDBG2ii+QZIEmYx8VdxA==", + "dependencies": { + "deepmerge": "^4.2.2", + "escape-string-regexp": "^4.0.0", + "htmlparser2": "^8.0.0", + "is-plain-object": "^5.0.0", + "parse-srcset": "^1.0.2", + "postcss": "^8.3.11" + } + }, "node_modules/saxes": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/saxes/-/saxes-5.0.1.tgz", @@ -2509,6 +2702,14 @@ "node": ">=10" } }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/sqlstring": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/sqlstring/-/sqlstring-2.3.3.tgz", diff --git a/package.json b/package.json index 3269ed0..c5cb4f1 100644 --- a/package.json +++ b/package.json @@ -12,6 +12,7 @@ "license": "ISC", "dependencies": { "bcryptjs": "^3.0.2", + "cookie-parser": "^1.4.7", "cors": "^2.8.5", "csv-parser": "^3.2.0", "dotenv": "^17.2.3", @@ -25,6 +26,7 @@ "mysql2": "^3.15.2", "nodemailer": "^7.0.10", "nodemon": "^3.1.10", + "sanitize-html": "^2.17.0", "sequelize": "^6.37.7", "swagger-jsdoc": "^6.2.8", "swagger-ui-express": "^5.0.1", diff --git a/server.js b/server.js index 59a9cb4..4936e76 100644 --- a/server.js +++ b/server.js @@ -1,21 +1,52 @@ const express = require("express"); const cors = require("cors"); -const helmet = require("helmet"); +const helmet = require("helmet"); const morgan = require("morgan"); const swaggerUi = require("swagger-ui-express"); const swaggerJsdoc = require("swagger-jsdoc"); const routes = require("./app/routes/routes"); const db = require("./app/models"); const path = require("path"); +const cookieParser = require("cookie-parser"); require("dotenv").config(); const app = express(); + app.use(express.json()); app.use(cors()); app.use(helmet()); app.use(morgan("dev")); +app.use(cookieParser()); +// // Swagger setup +// const swaggerOptions = { +// definition: { +// openapi: "3.0.0", +// info: { +// title: "FCSC IPI Survey", +// version: "1.0.0", +// description: "Federal Competitiveness and Statistics Centre (FCSC) - Industrial Production Index (IPI)", +// }, +// components: { +// securitySchemes: { +// bearerAuth: { +// type: "http", +// scheme: "bearer", +// bearerFormat: "JWT", +// }, +// }, +// }, +// security: [ +// { +// bearerAuth: [], +// }, +// ], +// }, +// apis: ["./app/routes/*.js"], +// }; +// const swaggerDocs = swaggerJsdoc(swaggerOptions); +// app.use("/api-docs", swaggerUi.serve, swaggerUi.setup(swaggerDocs)); // Swagger setup const swaggerOptions = { definition: { @@ -27,23 +58,34 @@ const swaggerOptions = { }, components: { securitySchemes: { + // Optional: Still support Bearer for Swagger-only testing bearerAuth: { type: "http", scheme: "bearer", bearerFormat: "JWT", }, + appSignature: { + type: "apiKey", + in: "header", + name: "x-app-signature" + } }, }, - security: [ - { - bearerAuth: [], - }, - ], }, apis: ["./app/routes/*.js"], }; + const swaggerDocs = swaggerJsdoc(swaggerOptions); -app.use("/api-docs", swaggerUi.serve, swaggerUi.setup(swaggerDocs)); + +app.use( + "/api-docs", + swaggerUi.serve, + swaggerUi.setup(swaggerDocs, { + swaggerOptions: { + withCredentials: true, // 🔥 VERY IMPORTANT + }, + }) +); // Routes