diff --git a/app/controllers/establishment.controller.js b/app/controllers/establishment.controller.js index a03736a..1c7ed53 100644 --- a/app/controllers/establishment.controller.js +++ b/app/controllers/establishment.controller.js @@ -1637,8 +1637,18 @@ exports.establishmentBulkUpload = async (req, res) => { exports.downloadCompanyProfileSample = async (req, res) => { try { - const filePath = path.join(__dirname, "../uploads/company_profile_upload_sample.csv"); - return res.download(filePath, "company_profile_upload_sample.csv"); + const safeBasePath = path.resolve(__dirname, "../uploads"); + const safeFilePath = path.join(safeBasePath, "company_profile_upload_sample.csv"); + + // Verify file exists BEFORE sending + if (!fs.existsSync(safeFilePath)) { + return res.status(404).send({ + status: "failed", + message: "File not found", + }); + } + + return res.download(safeFilePath, "unit_master_sample.csv"); } catch (error) { return res.status(500).send({ status: "failed", message: error.message }); } diff --git a/app/controllers/unitMasterController.js b/app/controllers/unitMasterController.js index 44a96fd..7d779ae 100644 --- a/app/controllers/unitMasterController.js +++ b/app/controllers/unitMasterController.js @@ -179,7 +179,7 @@ exports.uploadUnitMasterFromCSV = async (req, res) => { if (!req.file) return res.status(400).send({ status: "failed", message: "No file uploaded." }); - const filePath = req.file.path; + const filePath = path.resolve(req.file.path); // Validate file type if (!req.file.originalname.endsWith(".csv")) { @@ -431,7 +431,7 @@ exports.uploadUnitMasterFromCSV = async (req, res) => { }); } catch (error) { console.error("Error uploading Unit Master CSV:", error); - if (req.file && fs.existsSync(req.file.path)) fs.unlinkSync(req.file.path); + if (req.file && fs.existsSync(path.resolve(req.file.path))) fs.unlinkSync(path.resolve(req.file.path)); return res.status(500).send({ status: "failed", message: "Error processing CSV file.", @@ -440,11 +440,33 @@ exports.uploadUnitMasterFromCSV = async (req, res) => { } }; +// exports.downloadUnitMasterFile = async (req, res) => { +// try { +// const filePath = path.join(__dirname, "../uploads/unit_master_sample.csv"); +// return res.download(filePath, "unit_master_sample.csv"); +// } catch (error) { +// return res.status(500).send({ status: "failed", message: error.message }); +// } +// }; + exports.downloadUnitMasterFile = async (req, res) => { try { - const filePath = path.join(__dirname, "../uploads/unit_master_sample.csv"); - return res.download(filePath, "unit_master_sample.csv"); + const safeBasePath = path.resolve(__dirname, "../uploads"); + const safeFilePath = path.join(safeBasePath, "unit_master_sample.csv"); + + // Verify file exists BEFORE sending + if (!fs.existsSync(safeFilePath)) { + return res.status(404).send({ + status: "failed", + message: "File not found", + }); + } + + return res.download(safeFilePath, "unit_master_sample.csv"); } catch (error) { - return res.status(500).send({ status: "failed", message: error.message }); + return res.status(500).send({ + status: "failed", + message: error.message, + }); } -}; \ No newline at end of file +}; diff --git a/app/controllers/user.controller.js b/app/controllers/user.controller.js index cecfb57..2c69c2f 100644 --- a/app/controllers/user.controller.js +++ b/app/controllers/user.controller.js @@ -17,7 +17,7 @@ exports.createUser = async (req, res) => { logger.info(`User created: ${email}`); - res.status(201).send({'status':"success",'message':"created successfully",'data': user }); + res.status(201).send({'status':"success",'message':"created successfully" }); } catch (err) { logger.error(err.message); diff --git a/app/models/establishment_user.model.js b/app/models/establishment_user.model.js index f58bcc4..99b2593 100644 --- a/app/models/establishment_user.model.js +++ b/app/models/establishment_user.model.js @@ -64,7 +64,31 @@ module.exports = (sequelize, DataTypes) => { { timestamps: false, tableName: "establishment_users", - } + }, + { + // 1. EXCLUDE sensitive fields from all queries + defaultScope: { + attributes: { exclude: ["password", "reset_otp", "reset_otp_expires_at"] }, + }, + + // 2. Ensure manual selections still hide sensitive fields + scopes: { + withSensitive: { + attributes: { include: ["password", "reset_otp", "reset_otp_expires_at"] }, + }, + }, + + // 3. Remove sensitive fields when converting to JSON + instanceMethods: { + toJSON() { + const values = { ...this.get() }; + delete values.password; + delete values.reset_otp; + delete values.reset_otp_expires_at; + return values; + }, + }, + } ); EstablishmentUser.associate = (models) => { diff --git a/app/models/user.model.js b/app/models/user.model.js index 1d12781..e91a87d 100644 --- a/app/models/user.model.js +++ b/app/models/user.model.js @@ -34,7 +34,32 @@ module.exports = (sequelize, DataTypes) => { type: DataTypes.BOOLEAN, defaultValue: true, }, - }); + }, + { + // 1. EXCLUDE sensitive fields from all queries + defaultScope: { + attributes: { exclude: ["password", "reset_otp", "reset_otp_expires_at"] }, + }, + + // 2. Ensure manual selections still hide sensitive fields + scopes: { + withSensitive: { + attributes: { include: ["password", "reset_otp", "reset_otp_expires_at"] }, + }, + }, + + // 3. Remove sensitive fields when converting to JSON + instanceMethods: { + toJSON() { + const values = { ...this.get() }; + delete values.password; + delete values.reset_otp; + delete values.reset_otp_expires_at; + return values; + }, + }, + } + ); return User; }; diff --git a/app/utils/sanitizeInput.js b/app/utils/sanitizeInput.js index c843d20..a1c92ed 100644 --- a/app/utils/sanitizeInput.js +++ b/app/utils/sanitizeInput.js @@ -23,9 +23,12 @@ function sanitizeValue(value) { return value; // numbers, booleans, null } + + module.exports = function sanitizeInput(req, res, next) { - if (req.body) { - req.body = sanitizeValue(req.body); - } + if (req.body) req.body = sanitizeValue(req.body); + if (req.query) req.query = sanitizeValue(req.query); + if (req.params) req.params = sanitizeValue(req.params); next(); }; + diff --git a/server.js b/server.js index 3e2a11d..5eccdbe 100644 --- a/server.js +++ b/server.js @@ -21,6 +21,23 @@ app.use( contentSecurityPolicy: false, }) ); +// prevent framing (clickjacking protection) +app.use( + helmet.frameguard({ + action: "deny", + }) +); + +// Add HSTS explicitly (fixes scanner warning) +if (process.env.NODE_ENV === "production") { + app.use( + helmet.hsts({ + maxAge: 31536000, // 1 year + includeSubDomains: true, + preload: true, + }) + ); +} app.use(morgan("dev")); app.use(sanitizeInput);