From 82ff5d1bcb8aa5a3feda5e7f94d71ba00d3a1a50 Mon Sep 17 00:00:00 2001 From: Salah Eddine Lalami <136928179+salahlalami@users.noreply.github.com> Date: Mon, 16 Mar 2026 18:21:13 +0100 Subject: [PATCH 1/4] Simplify vulnerability reporting section in SECURITY.md Removed duplicate reporting process for vulnerabilities via Huntr.dev. --- SECURITY.md | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/SECURITY.md b/SECURITY.md index fc8f79f40e..65720e41c3 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -29,23 +29,7 @@ This security policy covers the security of this repository and its code. If you 5. **Disclosure**: We will coordinate with you regarding the public disclosure of the vulnerability. We aim to release a security advisory with information about the issue and the fix. -6. **Credit**: If you report a vulnerability that is successfully fixed, we will credit you for your responsible disclosure in the security advisory unless you prefer to remain anonymous. -#### Option 2: Reporting via Huntr.dev - -Alternatively, you can report vulnerabilities through [Huntr.dev](https://huntr.dev). Follow these steps: - -1. **Submit Report**: Create a report for this repository on Huntr.dev, providing details of the vulnerability. Include a link to this repository in your report. - -2. **Confirmation**: We will be notified of your report on Huntr.dev and will acknowledge it within [X] business days. - -3. **Investigation**: We will investigate the issue, which may involve reproducing the vulnerability or seeking further information from you. - -4. **Resolution**: Once the vulnerability is confirmed, we will work to address it promptly and develop a fix. - -5. **Disclosure**: We will coordinate with you regarding the public disclosure of the vulnerability. We aim to release a security advisory with information about the issue and the fix. - -6. **Credit**: If you report a vulnerability that is successfully fixed, we will credit you for your responsible disclosure in the security advisory unless you prefer to remain anonymous. ### Safe Harbor From aad6893527b96d5969d6a199d749dc1a3814fd90 Mon Sep 17 00:00:00 2001 From: SouvickSarkar20 Date: Wed, 15 Apr 2026 15:24:53 +0530 Subject: [PATCH 2/4] security: fix unauthenticated PDF download and admin password update IDOR --- backend/src/app.js | 2 +- .../createUserController/updatePassword.js | 2 +- backend/src/handlers/downloadHandler/downloadPdf.js | 1 + 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/backend/src/app.js b/backend/src/app.js index 3611c5a018..f3d85b22b8 100644 --- a/backend/src/app.js +++ b/backend/src/app.js @@ -39,7 +39,7 @@ app.use(compression()); app.use('/api', coreAuthRouter); app.use('/api', adminAuth.isValidAuthToken, coreApiRouter); app.use('/api', adminAuth.isValidAuthToken, erpApiRouter); -app.use('/download', coreDownloadRouter); +app.use('/download', adminAuth.isValidAuthToken, coreDownloadRouter); app.use('/public', corePublicRouter); // If that above routes didnt work, we 404 them and forward to error handler diff --git a/backend/src/controllers/middlewaresControllers/createUserController/updatePassword.js b/backend/src/controllers/middlewaresControllers/createUserController/updatePassword.js index 963429ad39..a035baf895 100644 --- a/backend/src/controllers/middlewaresControllers/createUserController/updatePassword.js +++ b/backend/src/controllers/middlewaresControllers/createUserController/updatePassword.js @@ -35,7 +35,7 @@ const updatePassword = async (userModel, req, res) => { }; const resultPassword = await UserPassword.findOneAndUpdate( - { user: req.params.id, removed: false }, + { user: userProfile._id, removed: false }, { $set: UserPasswordData }, { new: true, // return the new result instead of the old one diff --git a/backend/src/handlers/downloadHandler/downloadPdf.js b/backend/src/handlers/downloadHandler/downloadPdf.js index a7f7399550..67cd36f9c7 100644 --- a/backend/src/handlers/downloadHandler/downloadPdf.js +++ b/backend/src/handlers/downloadHandler/downloadPdf.js @@ -8,6 +8,7 @@ module.exports = downloadPdf = async (req, res, { directory, id }) => { const Model = mongoose.model(modelName); const result = await Model.findOne({ _id: id, + removed: false, }).exec(); // Throw error if no result From 40f4c37d861efb4cadab8d70db2b2e8be18ca9d7 Mon Sep 17 00:00:00 2001 From: SouvickSarkar20 Date: Wed, 15 Apr 2026 15:33:44 +0530 Subject: [PATCH 3/4] security: fix private settings bypass and remaining vulnerabilities --- .../settingController/filter.js | 33 ++++++++++ .../settingController/index.js | 12 ++-- .../coreControllers/settingController/list.js | 63 +++++++++++++++++++ .../settingController/listBySettingKey.js | 4 +- .../coreControllers/settingController/read.js | 28 +++++++++ .../settingController/readBySettingKey.js | 2 + .../settingController/search.js | 36 +++++++++++ 7 files changed, 173 insertions(+), 5 deletions(-) create mode 100644 backend/src/controllers/coreControllers/settingController/filter.js create mode 100644 backend/src/controllers/coreControllers/settingController/list.js create mode 100644 backend/src/controllers/coreControllers/settingController/read.js create mode 100644 backend/src/controllers/coreControllers/settingController/search.js diff --git a/backend/src/controllers/coreControllers/settingController/filter.js b/backend/src/controllers/coreControllers/settingController/filter.js new file mode 100644 index 0000000000..f2fa71660e --- /dev/null +++ b/backend/src/controllers/coreControllers/settingController/filter.js @@ -0,0 +1,33 @@ +const mongoose = require('mongoose'); +const Model = mongoose.model('Setting'); + +const filter = async (req, res) => { + const { filter, equal } = req.query; + + let filterCondition = {}; + if (filter && equal !== undefined) { + filterCondition = { [filter]: equal }; + } + + const result = await Model.find({ + removed: false, + isPrivate: false, + ...filterCondition, + }).exec(); + + if (result.length > 0) { + return res.status(200).json({ + success: true, + result, + message: 'Successfully found all documents', + }); + } else { + return res.status(203).json({ + success: false, + result: [], + message: 'Collection is Empty', + }); + } +}; + +module.exports = filter; diff --git a/backend/src/controllers/coreControllers/settingController/index.js b/backend/src/controllers/coreControllers/settingController/index.js index e78a6c0b78..32460ae3f7 100644 --- a/backend/src/controllers/coreControllers/settingController/index.js +++ b/backend/src/controllers/coreControllers/settingController/index.js @@ -6,14 +6,18 @@ const readBySettingKey = require('./readBySettingKey'); const updateBySettingKey = require('./updateBySettingKey'); const updateManySetting = require('./updateManySetting'); const listAll = require('./listAll'); +const read = require('./read'); +const list = require('./list'); +const search = require('./search'); +const filter = require('./filter'); const settingMethods = { - read: crudController.read, + read: read, create: crudController.create, update: crudController.update, - list: crudController.list, - filter: crudController.filter, - search: crudController.search, + list: list, + filter: filter, + search: search, listAll: listAll, listBySettingKey, readBySettingKey, diff --git a/backend/src/controllers/coreControllers/settingController/list.js b/backend/src/controllers/coreControllers/settingController/list.js new file mode 100644 index 0000000000..c46c3ae6a9 --- /dev/null +++ b/backend/src/controllers/coreControllers/settingController/list.js @@ -0,0 +1,63 @@ +const mongoose = require('mongoose'); +const Model = mongoose.model('Setting'); + +const list = async (req, res) => { + const page = req.query.page || 1; + const limit = parseInt(req.query.items) || 10; + const skip = page * limit - limit; + + const { sortBy = 'enabled', sortValue = -1, filter, equal } = req.query; + + const fieldsArray = req.query.fields ? req.query.fields.split(',') : []; + + let fields = fieldsArray.length === 0 ? {} : { $or: [] }; + + for (const field of fieldsArray) { + fields.$or.push({ [field]: { $regex: new RegExp(req.query.q, 'i') } }); + } + + let filterCondition = {}; + if (filter && equal !== undefined) { + filterCondition = { [filter]: equal }; + } + + const resultsPromise = Model.find({ + removed: false, + isPrivate: false, + ...filterCondition, + ...fields, + }) + .skip(skip) + .limit(limit) + .sort({ [sortBy]: sortValue }) + .exec(); + + const countPromise = Model.countDocuments({ + removed: false, + isPrivate: false, + ...filterCondition, + ...fields, + }); + + const [result, count] = await Promise.all([resultsPromise, countPromise]); + const pages = Math.ceil(count / limit); + const pagination = { page, pages, count }; + + if (count > 0) { + return res.status(200).json({ + success: true, + result, + pagination, + message: 'Successfully found all documents', + }); + } else { + return res.status(203).json({ + success: true, + result: [], + pagination, + message: 'Collection is Empty', + }); + } +}; + +module.exports = list; diff --git a/backend/src/controllers/coreControllers/settingController/listBySettingKey.js b/backend/src/controllers/coreControllers/settingController/listBySettingKey.js index 78311ca638..941c314768 100644 --- a/backend/src/controllers/coreControllers/settingController/listBySettingKey.js +++ b/backend/src/controllers/coreControllers/settingController/listBySettingKey.js @@ -26,7 +26,9 @@ const listBySettingKey = async (req, res) => { let results = await Model.find({ ...settingsToShow, - }).where('removed', false); + removed: false, + isPrivate: false, + }); // If no results found, return document not found if (results.length >= 1) { diff --git a/backend/src/controllers/coreControllers/settingController/read.js b/backend/src/controllers/coreControllers/settingController/read.js new file mode 100644 index 0000000000..f17a442ae6 --- /dev/null +++ b/backend/src/controllers/coreControllers/settingController/read.js @@ -0,0 +1,28 @@ +const mongoose = require('mongoose'); +const Model = mongoose.model('Setting'); + +const read = async (req, res) => { + // Find document by id + const result = await Model.findOne({ + _id: req.params.id, + removed: false, + isPrivate: false, + }).exec(); + // If no results found, return document not found + if (!result) { + return res.status(404).json({ + success: false, + result: null, + message: 'No document found ', + }); + } else { + // Return success resposne + return res.status(200).json({ + success: true, + result, + message: 'we found this document ', + }); + } +}; + +module.exports = read; diff --git a/backend/src/controllers/coreControllers/settingController/readBySettingKey.js b/backend/src/controllers/coreControllers/settingController/readBySettingKey.js index 476e29a081..c3ba216b19 100644 --- a/backend/src/controllers/coreControllers/settingController/readBySettingKey.js +++ b/backend/src/controllers/coreControllers/settingController/readBySettingKey.js @@ -16,6 +16,8 @@ const readBySettingKey = async (req, res) => { const result = await Model.findOne({ settingKey, + removed: false, + isPrivate: false, }); // If no results found, return document not found diff --git a/backend/src/controllers/coreControllers/settingController/search.js b/backend/src/controllers/coreControllers/settingController/search.js new file mode 100644 index 0000000000..3d38416111 --- /dev/null +++ b/backend/src/controllers/coreControllers/settingController/search.js @@ -0,0 +1,36 @@ +const mongoose = require('mongoose'); +const Model = mongoose.model('Setting'); + +const search = async (req, res) => { + const fieldsArray = req.query.fields ? req.query.fields.split(',') : ['settingKey', 'settingCategory']; + + const fields = { $or: [] }; + + for (const field of fieldsArray) { + fields.$or.push({ [field]: { $regex: new RegExp(req.query.q, 'i') } }); + } + + let results = await Model.find({ + ...fields, + removed: false, + isPrivate: false, + }) + .limit(20) + .exec(); + + if (results.length >= 1) { + return res.status(200).json({ + success: true, + result: results, + message: 'Successfully found all documents', + }); + } else { + return res.status(202).json({ + success: false, + result: [], + message: 'No document found by this request', + }); + } +}; + +module.exports = search; From dbd0b88d7dc777efd7aa62268ee8cf3475a34b1d Mon Sep 17 00:00:00 2001 From: SouvickSarkar20 Date: Wed, 15 Apr 2026 16:05:45 +0530 Subject: [PATCH 4/4] fix: reset success state in auth reducer to fix password reset redirect --- frontend/src/redux/auth/reducer.js | 9 ++++++--- frontend/src/redux/auth/types.js | 3 --- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/frontend/src/redux/auth/reducer.js b/frontend/src/redux/auth/reducer.js index 1a99fa9c60..36c0ebf602 100644 --- a/frontend/src/redux/auth/reducer.js +++ b/frontend/src/redux/auth/reducer.js @@ -14,6 +14,7 @@ const authReducer = (state = INITIAL_STATE, action) => { ...state, isLoggedIn: false, isLoading: true, + isSuccess: false, }; case actionTypes.REQUEST_FAILED: return INITIAL_STATE; @@ -38,12 +39,14 @@ const authReducer = (state = INITIAL_STATE, action) => { case actionTypes.LOGOUT_FAILED: return { - current: action.payload, - isLoggedIn: true, + ...state, isLoading: false, - isSuccess: true, + isSuccess: false, }; + case actionTypes.RESET_STATE: + return INITIAL_STATE; + default: return state; } diff --git a/frontend/src/redux/auth/types.js b/frontend/src/redux/auth/types.js index 95fb10edf6..dd384e8cb8 100644 --- a/frontend/src/redux/auth/types.js +++ b/frontend/src/redux/auth/types.js @@ -1,6 +1,3 @@ -export const FAILED_REQUEST = 'AUTH_FAILED_REQUEST'; -export const LOADING_REQUEST = 'AUTH_LOADING_REQUEST'; - export const LOGIN_SUCCESS = 'AUTH_LOGIN_SUCCESS'; export const REGISTER_SUCCESS = 'AUTH_REGISTER_SUCCESS';