From 7ad1ae213b860275a4e912cc1d3972371effda9a Mon Sep 17 00:00:00 2001 From: Nevil Date: Wed, 13 Mar 2024 19:46:36 +0530 Subject: [PATCH 01/10] fixed an issue with index.js --- src/middlewares/validator.js | 5 ++- src/routes/index.js | 87 +++++++++++++++++++++++++++++++++--- 2 files changed, 85 insertions(+), 7 deletions(-) diff --git a/src/middlewares/validator.js b/src/middlewares/validator.js index 657baf27f..10e50282e 100644 --- a/src/middlewares/validator.js +++ b/src/middlewares/validator.js @@ -7,7 +7,10 @@ module.exports = (req, res, next) => { try { - require(`@validators/${req.params.version}/${req.params.controller}`)[req.params.method](req) + const version = (req.params.version.match(/^v\d+$/) || [])[0] // Match version like v1, v2, etc. + const controllerName = (req.params.controller.match(/^[a-zA-Z0-9_-]+$/) || [])[0] // Allow only alphanumeric characters, underscore, and hyphen + const method = (req.params.method.match(/^[a-zA-Z0-9]+$/) || [])[0] // Allow only alphanumeric characters + require(`@validators/${version}/${controllerName}`)[method](req) } catch {} next() } diff --git a/src/routes/index.js b/src/routes/index.js index 6e6928d31..55d2dd2a6 100644 --- a/src/routes/index.js +++ b/src/routes/index.js @@ -12,15 +12,92 @@ const expressValidator = require('express-validator') const fs = require('fs') const { elevateLog, correlationId } = require('elevate-logger') const logger = elevateLog.init() +const path = require('path') module.exports = (app) => { app.use(authenticator) app.use(pagination) app.use(expressValidator()) + async function getAllowedControllers(directoryPath) { + try { + const getAllFilesAndDirectories = (dir) => { + let filesAndDirectories = [] + fs.readdirSync(dir).forEach((item) => { + const itemPath = path.join(dir, item) + const stat = fs.statSync(itemPath) + if (stat.isDirectory()) { + filesAndDirectories.push({ + name: item, + type: 'directory', + path: itemPath, + }) + filesAndDirectories = filesAndDirectories.concat(getAllFilesAndDirectories(itemPath)) + } else { + filesAndDirectories.push({ + name: item, + type: 'file', + path: itemPath, + }) + } + }) + return filesAndDirectories + } + + const allFilesAndDirectories = getAllFilesAndDirectories(directoryPath) + const allowedControllers = allFilesAndDirectories + .filter((item) => item.type === 'file' && item.name.endsWith('.js')) + .map((item) => path.basename(item.name, '.js')) // Remove the ".js" extension + const allowedVersions = allFilesAndDirectories + .filter((item) => item.type === 'directory') + .map((item) => item.name) + + return { + allowedControllers, + allowedVersions, + } + } catch (err) { + console.error('Unable to scan directory:', err) + return { + allowedControllers: [], + directories: [], + } + } + } async function router(req, res, next) { let controllerResponse let validationError + const version = (req.params.version.match(/^v\d+$/) || [])[0] // Match version like v1, v2, etc. + const controllerName = (req.params.controller.match(/^[a-zA-Z0-9_-]+$/) || [])[0] // Allow only alphanumeric characters, underscore, and hyphen + const file = req.params.file ? (req.params.file.match(/^[a-zA-Z0-9_-]+$/) || [])[0] : null // Same validation as controller, or null if file is not provided + const method = (req.params.method.match(/^[a-zA-Z0-9]+$/) || [])[0] // Allow only alphanumeric characters + try { + if (!version || !controllerName || !method || (req.params.file && !file)) { + // Invalid input, return an error response + const error = new Error('Invalid Path') + error.statusCode = 400 + throw error + } + + const directoryPath = path.resolve(__dirname, '..', 'controllers') + + const { allowedControllers, allowedVersions } = await getAllowedControllers(directoryPath) + + // Validate version + if (!allowedVersions.includes(version)) { + const error = new Error('Invalid version.') + error.statusCode = 400 + throw error + } + // Validate controller + if (!allowedControllers.includes(controllerName)) { + const error = new Error('Invalid controller.') + error.statusCode = 400 + throw error + } + } catch (error) { + return next(error) + } /* Check for input validation error */ try { @@ -53,16 +130,14 @@ module.exports = (app) => { '.js' ) if (folderExists) { - controller = require(`@controllers/${req.params.version}/${req.params.controller}/${req.params.file}`) + controller = require(`@controllers/${version}/${controllerName}/${file}`) } else { - controller = require(`@controllers/${req.params.version}/${req.params.controller}`) + controller = require(`@controllers/${version}/${controllerName}`) } } else { - controller = require(`@controllers/${req.params.version}/${req.params.controller}`) + controller = require(`@controllers/${version}/${controllerName}`) } - controllerResponse = new controller()[req.params.method] - ? await new controller()[req.params.method](req) - : next() + controllerResponse = new controller()[method] ? await new controller()[method](req) : next() } catch (error) { // If controller or service throws some random error return next(error) From 195eb24187cb3fe4550786711a2c44c20a3c6144 Mon Sep 17 00:00:00 2001 From: sumanvpacewisdom Date: Tue, 19 Mar 2024 15:32:27 +0530 Subject: [PATCH 02/10] Audit Bug - 1148 , 1152 , 1153 --- src/constants/common.js | 2 +- src/envVariables.js | 4 ++-- src/locales/en.json | 4 +++- src/services/org-admin.js | 13 +++++++++++++ src/validators/v1/account.js | 20 ++++++++++++++++++-- src/validators/v1/admin.js | 10 +++++++++- 6 files changed, 46 insertions(+), 7 deletions(-) diff --git a/src/constants/common.js b/src/constants/common.js index 159efde6f..ce9011075 100644 --- a/src/constants/common.js +++ b/src/constants/common.js @@ -28,7 +28,7 @@ module.exports = { '/user/v1/user-role/default', ], notificationEmailType: 'email', - accessTokenExpiry: `${process.env.ACCESS_TOKEN_EXPIRY}d`, + accessTokenExpiry: process.env.ACCESS_TOKEN_EXPIRY, refreshTokenExpiry: `${process.env.REFRESH_TOKEN_EXPIRY}d`, refreshTokenExpiryInMs: Number(process.env.REFRESH_TOKEN_EXPIRY) * 24 * 60 * 60 * 1000, refreshTokenLimit: 3, diff --git a/src/envVariables.js b/src/envVariables.js index e5ad54689..b61ff67ae 100644 --- a/src/envVariables.js +++ b/src/envVariables.js @@ -96,11 +96,11 @@ let enviromentVariables = { optional: process.env.CLOUD_STORAGE === 'AZURE' ? false : true, }, ACCESS_TOKEN_EXPIRY: { - message: 'Required access token expiry in days', + message: 'Required access token expiry', optional: false, }, REFRESH_TOKEN_EXPIRY: { - message: 'Required refresh token expiry in days', + message: 'Required refresh token expiry', optional: false, }, API_DOC_URL: { diff --git a/src/locales/en.json b/src/locales/en.json index 9cba64a1a..b5dad36d7 100644 --- a/src/locales/en.json +++ b/src/locales/en.json @@ -113,5 +113,7 @@ "ROLES_HAS_EMPTY_LIST": "Empty roles list", "COLUMN_DOES_NOT_EXISTS": "Role column does not exists", "PERMISSION_DENIED": "You do not have the required permissions to access this resource. Please contact your administrator for assistance.", - "RELATED_ORG_REMOVAL_FAILED": "Requested organization not related the organization. Please check the values." + "RELATED_ORG_REMOVAL_FAILED": "Requested organization not related the organization. Please check the values.", + "INAVLID_ORG_ROLE_REQ": "Invalid organisation request" + } diff --git a/src/services/org-admin.js b/src/services/org-admin.js index 3b5d136bc..bd496d222 100644 --- a/src/services/org-admin.js +++ b/src/services/org-admin.js @@ -237,6 +237,19 @@ module.exports = class OrgAdminHelper { const requestId = bodyData.request_id delete bodyData.request_id + const requestDetail = await orgRoleReqQueries.requestDetails({ + id: requestId, + organization_id: tokenInformation.organization_id, + }) + + if (requestDetail.status !== common.REQUESTED_STATUS) { + return responses.failureResponse({ + message: 'INAVLID_ORG_ROLE_REQ', + statusCode: httpStatusCode.bad_request, + responseCode: 'CLIENT_ERROR', + }) + } + bodyData.handled_by = tokenInformation.id const rowsAffected = await orgRoleReqQueries.update( { id: requestId, organization_id: tokenInformation.organization_id }, diff --git a/src/validators/v1/account.js b/src/validators/v1/account.js index 449860c2d..260c05c98 100644 --- a/src/validators/v1/account.js +++ b/src/validators/v1/account.js @@ -25,7 +25,15 @@ module.exports = { .withMessage('email is invalid') .normalizeEmail({ gmail_remove_dots: false }) - req.checkBody('password').trim().notEmpty().withMessage('password field is empty') + req.checkBody('password') + .notEmpty() + .withMessage('Password field is empty') + .matches(/^(?=.*[A-Z])(?=.*\d)(?=.*[!@#$%^&*()_+{}|:"<>?~`\-=[\];',.\/])[^ ]{10,}$/) + .withMessage( + 'Password must have at least one uppercase letter, one number, one special character, and be at least 10 characters long' + ) + .custom((value) => !/\s/.test(value)) + .withMessage('Password cannot contain spaces') if (req.body.role) { req.checkBody('role').trim().not().isIn([common.ADMIN_ROLE]).withMessage("User does't have admin access") @@ -64,7 +72,15 @@ module.exports = { resetPassword: (req) => { req.checkBody('email').notEmpty().withMessage('email field is empty').isEmail().withMessage('email is invalid') - req.checkBody('password').notEmpty().withMessage('password field is empty') + req.checkBody('password') + .notEmpty() + .withMessage('Password field is empty') + .matches(/^(?=.*[A-Z])(?=.*\d)(?=.*[!@#$%^&*()_+{}|:"<>?~`\-=[\];',.\/])[^ ]{10,}$/) + .withMessage( + 'Password must have at least one uppercase letter, one number, one special character, and be at least 10 characters long' + ) + .custom((value) => !/\s/.test(value)) + .withMessage('Password cannot contain spaces') req.checkBody('otp') .notEmpty() diff --git a/src/validators/v1/admin.js b/src/validators/v1/admin.js index 575be9190..75ffdbc89 100644 --- a/src/validators/v1/admin.js +++ b/src/validators/v1/admin.js @@ -27,7 +27,15 @@ module.exports = { .withMessage('email is invalid') .normalizeEmail() - req.checkBody('password').trim().notEmpty().withMessage('password field is empty') + req.checkBody('password') + .notEmpty() + .withMessage('Password field is empty') + .matches(/^(?=.*[A-Z])(?=.*\d)(?=.*[!@#$%^&*()_+{}|:"<>?~`\-=[\];',.\/])[^ ]{10,}$/) + .withMessage( + 'Password must have at least one uppercase letter, one number, one special character, and be at least 10 characters long' + ) + .custom((value) => !/\s/.test(value)) + .withMessage('Password cannot contain spaces') }, login: (req) => { From 861c2a0afacfa58b2f05fa57735e415c9319f5a8 Mon Sep 17 00:00:00 2001 From: vishnu Date: Tue, 19 Mar 2024 16:18:40 +0530 Subject: [PATCH 03/10] 1142 -security fix, downloadableUrl API --- src/.env.sample | 3 +++ src/envVariables.js | 5 +++++ src/generics/utils.js | 3 ++- 3 files changed, 10 insertions(+), 1 deletion(-) diff --git a/src/.env.sample b/src/.env.sample index ad7a7da79..858787335 100644 --- a/src/.env.sample +++ b/src/.env.sample @@ -167,3 +167,6 @@ GENERIC_INVITATION_EMAIL_TEMPLATE_CODE=generic_invite # Allowed host by CORS ALLOWED_HOST = "http://examplDomain.com" + +# Downloadabale url exipres after +DOWNLOAD_URL_EXPIRATION_DURATION = 120000 \ No newline at end of file diff --git a/src/envVariables.js b/src/envVariables.js index e5ad54689..5fc0ce707 100644 --- a/src/envVariables.js +++ b/src/envVariables.js @@ -237,6 +237,11 @@ let enviromentVariables = { optional: true, default: '*', }, + DOWNLOAD_URL_EXPIRATION_DURATION: { + message: 'Required downloadable url expiration time', + optional: true, + default: 3600000, + }, } let success = true diff --git a/src/generics/utils.js b/src/generics/utils.js index 7a57f1fc8..2e45d706f 100644 --- a/src/generics/utils.js +++ b/src/generics/utils.js @@ -53,8 +53,9 @@ const getDownloadableUrl = async (imgPath) => { bucketName: process.env.DEFAULT_GCP_BUCKET_NAME, gcpProjectId: process.env.GCP_PROJECT_ID, gcpJsonFilePath: path.join(__dirname, '../', process.env.GCP_PATH), + expiry: Date.now() + parseFloat(process.env.DOWNLOAD_URL_EXPIRATION_DURATION), } - imgPath = await GcpFileHelper.getDownloadableUrl(options) + imgPath = await GcpFileHelper.getSignedDownloadableUrl(options) } else if (process.env.CLOUD_STORAGE === 'AWS') { const options = { destFilePath: imgPath, From 0e825a01ece75029fa87859adbee2f6785e83448 Mon Sep 17 00:00:00 2001 From: vishnu Date: Tue, 19 Mar 2024 16:24:39 +0530 Subject: [PATCH 04/10] package version updated --- src/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/package.json b/src/package.json index c6da7f3ee..39f87028e 100644 --- a/src/package.json +++ b/src/package.json @@ -37,7 +37,7 @@ "crypto": "^1.0.1", "csvtojson": "^2.0.10", "dotenv": "^10.0.0", - "elevate-cloud-storage": "2.0.0", + "elevate-cloud-storage": "2.6.1", "elevate-encryption": "^1.0.1", "elevate-logger": "^3.1.0", "elevate-node-cache": "^1.0.6", From 5817165679a96671c4b515f8b55fc5f688ddfdae Mon Sep 17 00:00:00 2001 From: sumanvpacewisdom Date: Wed, 20 Mar 2024 16:21:17 +0530 Subject: [PATCH 05/10] Comment Changes regarding the password --- src/envVariables.js | 11 +++++++++++ src/validators/v1/account.js | 14 +++++--------- src/validators/v1/admin.js | 8 +++----- 3 files changed, 19 insertions(+), 14 deletions(-) diff --git a/src/envVariables.js b/src/envVariables.js index b61ff67ae..0a9a29855 100644 --- a/src/envVariables.js +++ b/src/envVariables.js @@ -237,6 +237,17 @@ let enviromentVariables = { optional: true, default: '*', }, + PASSWORD_POLICY_REGEX: { + message: 'Required password policy', + optional: true, + default: '/^(?=.*[A-Z])(?=.*d)(?=.*[!@#$%^&*()_+{}|:<>?~`-=[];,./])[^ ]{11,}$/', + }, + PASSWORD_POLICY_MESSAGE: { + message: 'Required password policy message', + optional: true, + default: + 'Password must have at least one uppercase letter, one number, one special character, and be at least 10 characters long', + }, } let success = true diff --git a/src/validators/v1/account.js b/src/validators/v1/account.js index 260c05c98..aa906017c 100644 --- a/src/validators/v1/account.js +++ b/src/validators/v1/account.js @@ -25,13 +25,11 @@ module.exports = { .withMessage('email is invalid') .normalizeEmail({ gmail_remove_dots: false }) - req.checkBody('password') + req.checkBody('password') .notEmpty() .withMessage('Password field is empty') - .matches(/^(?=.*[A-Z])(?=.*\d)(?=.*[!@#$%^&*()_+{}|:"<>?~`\-=[\];',.\/])[^ ]{10,}$/) - .withMessage( - 'Password must have at least one uppercase letter, one number, one special character, and be at least 10 characters long' - ) + .matches(process.env.PASSWORD_POLICY_REGEX) + .withMessage(process.env.PASSWORD_POLICY_MESSAGE) .custom((value) => !/\s/.test(value)) .withMessage('Password cannot contain spaces') @@ -75,10 +73,8 @@ module.exports = { req.checkBody('password') .notEmpty() .withMessage('Password field is empty') - .matches(/^(?=.*[A-Z])(?=.*\d)(?=.*[!@#$%^&*()_+{}|:"<>?~`\-=[\];',.\/])[^ ]{10,}$/) - .withMessage( - 'Password must have at least one uppercase letter, one number, one special character, and be at least 10 characters long' - ) + .matches(process.env.PASSWORD_POLICY_REGEX) + .withMessage(process.env.PASSWORD_POLICY_MESSAGE) .custom((value) => !/\s/.test(value)) .withMessage('Password cannot contain spaces') diff --git a/src/validators/v1/admin.js b/src/validators/v1/admin.js index 75ffdbc89..1877d9f36 100644 --- a/src/validators/v1/admin.js +++ b/src/validators/v1/admin.js @@ -27,13 +27,11 @@ module.exports = { .withMessage('email is invalid') .normalizeEmail() - req.checkBody('password') + req.checkBody('password') .notEmpty() .withMessage('Password field is empty') - .matches(/^(?=.*[A-Z])(?=.*\d)(?=.*[!@#$%^&*()_+{}|:"<>?~`\-=[\];',.\/])[^ ]{10,}$/) - .withMessage( - 'Password must have at least one uppercase letter, one number, one special character, and be at least 10 characters long' - ) + .matches(process.env.PASSWORD_POLICY_REGEX) + .withMessage(process.env.PASSWORD_POLICY_MESSAGE) .custom((value) => !/\s/.test(value)) .withMessage('Password cannot contain spaces') }, From a607cb4f79ede831e6e78649fe4660b8346ae8a4 Mon Sep 17 00:00:00 2001 From: adithya_dinesh Date: Thu, 21 Mar 2024 12:16:20 +0530 Subject: [PATCH 06/10] user update blacklist updated --- src/constants/blacklistConfig.js | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/constants/blacklistConfig.js b/src/constants/blacklistConfig.js index 72c39e26f..b48a1dcae 100644 --- a/src/constants/blacklistConfig.js +++ b/src/constants/blacklistConfig.js @@ -358,11 +358,18 @@ const userRole = { const user = { update: [ 'id', + 'email', + 'email_verified', + 'password', + 'about', 'share_link', + 'status', 'last_logged_in_at', + 'has_accepted_terms_and_conditions', 'refresh_tokens', + 'languages', + 'preferred_language', 'organization_id', - 'roles', 'custom_entity_text', 'meta', ], From c633acb182abfa7bf84ad19e7545f61a34c82011 Mon Sep 17 00:00:00 2001 From: adithya_dinesh Date: Thu, 21 Mar 2024 12:24:41 +0530 Subject: [PATCH 07/10] user update blacklist updated - Added roles --- src/constants/blacklistConfig.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/constants/blacklistConfig.js b/src/constants/blacklistConfig.js index b48a1dcae..f9bad8bcc 100644 --- a/src/constants/blacklistConfig.js +++ b/src/constants/blacklistConfig.js @@ -370,6 +370,7 @@ const user = { 'languages', 'preferred_language', 'organization_id', + 'roles', 'custom_entity_text', 'meta', ], From 182188c6b24aef06b52526d4c0106388d6d6b836 Mon Sep 17 00:00:00 2001 From: Nevil Date: Thu, 21 Mar 2024 18:28:30 +0530 Subject: [PATCH 08/10] added cloud-services to valid list --- src/routes/index.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/routes/index.js b/src/routes/index.js index 55d2dd2a6..b73743c62 100644 --- a/src/routes/index.js +++ b/src/routes/index.js @@ -89,7 +89,9 @@ module.exports = (app) => { error.statusCode = 400 throw error } + // Validate controller + allowedControllers.push('cloud-services') if (!allowedControllers.includes(controllerName)) { const error = new Error('Invalid controller.') error.statusCode = 400 From e180323dfcf503fd11366f8b49342e6da505113c Mon Sep 17 00:00:00 2001 From: adithya_dinesh Date: Fri, 22 Mar 2024 12:49:16 +0530 Subject: [PATCH 09/10] user update blacklist updated - changes --- src/constants/blacklistConfig.js | 1 - src/validators/v1/organization.js | 2 ++ src/validators/v1/user.js | 1 + 3 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/constants/blacklistConfig.js b/src/constants/blacklistConfig.js index 19a3f876d..cf4038180 100644 --- a/src/constants/blacklistConfig.js +++ b/src/constants/blacklistConfig.js @@ -84,7 +84,6 @@ const account = { registrationOtp: [ 'id', 'email_verified', - 'name', 'gender', 'location', 'about', diff --git a/src/validators/v1/organization.js b/src/validators/v1/organization.js index 58651f2f2..9a0bcf838 100644 --- a/src/validators/v1/organization.js +++ b/src/validators/v1/organization.js @@ -21,6 +21,7 @@ module.exports = { .trim() .notEmpty() .withMessage('description field is empty') + .not() .matches(/(\b)(on\S+)(\s*)=|javascript:|<(|\/|[^\/>][^>]+|\/[^>][^>]+)>/gi) .withMessage('invalid description') req.checkBody('domains').trim().notEmpty().withMessage('domains field is empty') @@ -42,6 +43,7 @@ module.exports = { .trim() .notEmpty() .withMessage('description field is empty') + .not() .matches(/(\b)(on\S+)(\s*)=|javascript:|<(|\/|[^\/>][^>]+|\/[^>][^>]+)>/gi) .withMessage('invalid description') }, diff --git a/src/validators/v1/user.js b/src/validators/v1/user.js index 40feb3f7e..a0ceb2c2f 100644 --- a/src/validators/v1/user.js +++ b/src/validators/v1/user.js @@ -38,6 +38,7 @@ module.exports = { .trim() .notEmpty() .withMessage('about field is empty') + .not() .matches(/(\b)(on\S+)(\s*)=|javascript:|<(|\/|[^\/>][^>]+|\/[^>][^>]+)>/gi) .withMessage('invalid about') From c7241baa106c640cf774c33602f43b4abdf9c6b9 Mon Sep 17 00:00:00 2001 From: adithya_dinesh Date: Fri, 22 Mar 2024 12:53:46 +0530 Subject: [PATCH 10/10] user update blacklist updated - changes --- src/constants/blacklistConfig.js | 8 -------- 1 file changed, 8 deletions(-) diff --git a/src/constants/blacklistConfig.js b/src/constants/blacklistConfig.js index cf4038180..f6e21fc1f 100644 --- a/src/constants/blacklistConfig.js +++ b/src/constants/blacklistConfig.js @@ -327,17 +327,9 @@ const userRole = { const user = { update: [ 'id', - 'email', - 'email_verified', - 'password', - 'about', 'share_link', - 'status', 'last_logged_in_at', - 'has_accepted_terms_and_conditions', 'refresh_tokens', - 'languages', - 'preferred_language', 'organization_id', 'roles', 'custom_entity_text',