Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
f6b1d41
Add distribution for organization_registration_codes table
nevil-mathew Oct 6, 2025
0b40f74
Add 'code' field to organization update blacklist
nevil-mathew Oct 6, 2025
463ecb2
Refactor eventBodyDTO to use oldValues and newValues for organization…
nevil-mathew Oct 6, 2025
22cffa9
Add extractDelta function and utility logging in eventBroadcasterMain
nevil-mathew Oct 6, 2025
ada71ae
Add raw option to tenant update queries for consistent response format
nevil-mathew Oct 6, 2025
65b4262
Refactor eventBodyDTO to use oldValues and newValues for tenant updat…
nevil-mathew Oct 7, 2025
c4cde5b
Refactor eventBodyDTO to use oldValues and newValues; update updateRo…
nevil-mathew Oct 7, 2025
1503ff2
Add error handling for tenant update failures in update method
nevil-mathew Oct 7, 2025
2b1d7c0
feat(auth): allow admin role to override tenant and org
nevil-mathew Oct 9, 2025
7388165
feat(env): add optional headers for ORG_ID, ORG_CODE, and TENANT_CODE
nevil-mathew Oct 9, 2025
490a558
feat(locales): add organization header messages and validation for or…
nevil-mathew Oct 9, 2025
c09c718
fix(locales): correct placeholder syntax in organization header message
nevil-mathew Oct 9, 2025
65f352b
feat(constants): update ORG_CODE_HEADER and TENANT_CODE_HEADER to use…
nevil-mathew Oct 9, 2025
e72da9e
fix(auth): replace organization_id with parsedOrgId in token decoding
nevil-mathew Oct 9, 2025
4095b0f
fix(auth): use strict equality for admin role check
nevil-mathew Oct 9, 2025
43dde73
fix(auth): remove redundant assignment of decodedToken to request object
nevil-mathew Oct 9, 2025
d909cf2
refactor(headers): use env header names and lowercase defaults
nevil-mathew Oct 13, 2025
15ad6de
feat(admin): add raw SELECT query execution with pagination
nevil-mathew Oct 13, 2025
1d8fbae
refactor(headers): use common constants for header names
nevil-mathew Oct 13, 2025
78168ee
feat(auth): validate org and tenant code in authenticator
nevil-mathew Oct 13, 2025
485d9d0
Merge pull request #837 from ELEVATE-Project/update-events
nevil-mathew Oct 14, 2025
4299978
refactor(auth): remove orgId header requirement for admin override
nevil-mathew Oct 14, 2025
091cf7b
updated: response message
nevil-mathew Oct 14, 2025
40ac766
refactor(auth): simplify tenant filter construction logic
nevil-mathew Oct 15, 2025
b818c7c
Merge pull request #839 from ELEVATE-Project/admin-override-tenant-org
nevil-mathew Oct 15, 2025
68ce70b
feat(admin): add rawQueryUtils for secure query validation
nevil-mathew Oct 15, 2025
d32a714
updated: hasForbiddenPatterns
nevil-mathew Oct 15, 2025
a1e666f
Merge branch 'develop' of https://github.com/ELEVATE-Project/user int…
nevil-mathew Oct 15, 2025
88d9333
fix(auth): ensure decodedToken set and next() called properly
nevil-mathew Oct 15, 2025
65b1739
updated: getPaginationParams to handle default values
nevil-mathew Oct 15, 2025
f01b6df
updated: error response message
nevil-mathew Oct 15, 2025
debdaa4
add: comment for early return for admin users.
nevil-mathew Oct 15, 2025
3a77d3d
add: explicit role validation
nevil-mathew Oct 15, 2025
3a29ad6
add: validation for executeRawQuery
nevil-mathew Oct 15, 2025
0469661
fix: message
nevil-mathew Oct 15, 2025
07cfafd
Merge pull request #841 from ELEVATE-Project/add-db-find-api
nevil-mathew Oct 15, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/constants/blacklistConfig.js
Original file line number Diff line number Diff line change
Expand Up @@ -299,6 +299,7 @@ const organization = {
update: [
'id',
'org_admin',
'code',
'tenant_code',
'parent_id',
'related_orgs',
Expand Down
5 changes: 3 additions & 2 deletions src/constants/common.js
Original file line number Diff line number Diff line change
Expand Up @@ -102,8 +102,9 @@ module.exports = {
READ_ACCESS: 'r',
TYPE_ALL: 'all',
ENGLISH_LANGUGE_CODE: 'en',
ORG_CODE_HEADER: 'organizationcode',
TENANT_CODE_HEADER: 'tenantcode',
ORG_CODE_HEADER: process.env.ORG_CODE_HEADER_NAME.toLowerCase(),
ORG_ID_HEADER: process.env.ORG_ID_HEADER_NAME.toLowerCase(),
TENANT_CODE_HEADER: process.env.TENANT_CODE_HEADER_NAME.toLowerCase(),
DELETE_METHOD: 'DELETE',
SEQUELIZE_FOREIGN_KEY_CONSTRAINT_ERROR: 'SequelizeForeignKeyConstraintError',
BULK_INVITATION_VALIDITY: '604800000', //SET to one week by default if not set by tenant (In Sec),
Expand Down
34 changes: 32 additions & 2 deletions src/controllers/v1/admin.js
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ module.exports = class Admin {
req.body.organization_id,
req.decodedToken.id,
req.body?.identifier,
req.headers?.['tenant-id'],
req.headers?.[common.TENANT_CODE_HEADER],
req.body?.phone_code
)
return orgAdminCreation
Expand Down Expand Up @@ -154,7 +154,7 @@ module.exports = class Admin {

const result = await adminService.deactivateOrg(
req.params.id,
req.headers?.['tenant-id'],
req.headers?.[common.TENANT_CODE_HEADER],
req.decodedToken.id
)
return result
Expand Down Expand Up @@ -196,6 +196,36 @@ module.exports = class Admin {
}
}

/**
* Execute raw SELECT query with pagination
* @method
* @name executeRawQuery
* @param {Object} req - Request object containing query in body and pagination params
* @param {String} req.body.query - Raw SQL SELECT query
* @param {Number} req.pageNo - Page number
* @param {Number} req.pageSize - Page size limit
* @returns {JSON} - Paginated query results
*/
async executeRawQuery(req) {
try {
if (!utilsHelper.validateRoleAccess(req.decodedToken.roles, common.ADMIN_ROLE)) {
throw responses.failureResponse({
message: 'USER_IS_NOT_A_ADMIN',
statusCode: httpStatusCode.bad_request,
responseCode: 'CLIENT_ERROR',
})
}

const { query } = req.body

const pageNo = req.pageNo
const pageSize = req.pageSize

return await adminService.executeRawQuery(query, req.decodedToken.id, pageNo, pageSize)
} catch (error) {
return error
}
}
async triggerViewRebuild(req) {
try {
if (!req.decodedToken.roles.some((role) => role.title === common.ADMIN_ROLE)) {
Expand Down
3 changes: 2 additions & 1 deletion src/controllers/v1/form.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

// Dependencies
const formsService = require('@services/form')
const common = require('@constants/common')

module.exports = class Form {
/**
Expand Down Expand Up @@ -100,7 +101,7 @@ module.exports = class Form {
req.params.id,
params,
req?.decodedToken?.organization_id || null,
req?.decodedToken?.tenant_code || req?.headers?.tenant_code || null,
req?.decodedToken?.tenant_code || req?.headers?.[common.TENANT_CODE_HEADER] || null,
domain
)
return form
Expand Down
3 changes: 2 additions & 1 deletion src/controllers/v1/public.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
const publicService = require('@services/public')
const { getDomainFromRequest } = require('@utils/domain')
const common = require('@constants/common')

module.exports = class Public {
async branding(req) {
let domain = ''
let tenantCode = req?.headers?.tenantid || null
let tenantCode = req?.headers?.[common.TENANT_CODE_HEADER] || null
if (!tenantCode) {
domain = getDomainFromRequest(req)
}
Expand Down
4 changes: 2 additions & 2 deletions src/controllers/v1/tenant.js
Original file line number Diff line number Diff line change
Expand Up @@ -146,8 +146,8 @@ module.exports = class Tenant {
const tenant = await tenantService.userBulkUpload(
req.body.file_path,
req.decodedToken.id,
req.headers.organization,
req.headers.tenant,
req.headers?.[common.ORG_CODE_HEADER],
req.headers?.[common.TENANT_CODE_HEADER],
req?.body?.editable_fields,
req?.body?.upload_type.toUpperCase()
)
Expand Down
1 change: 1 addition & 0 deletions src/database/queries/tenants.js
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ exports.update = async (filter, updatedata) => {
return await Tenant.update(updatedata, {
where: filter,
returning: true,
raw: true,
})
} catch (error) {
throw error
Expand Down
1 change: 1 addition & 0 deletions src/distributionColumns.sql
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ SELECT create_distributed_table('user_sessions', 'tenant_code');
-- Organization-related tables
SELECT create_distributed_table('organization_email_domains', 'tenant_code');
SELECT create_distributed_table('organization_features', 'tenant_code');
SELECT create_distributed_table('organization_registration_codes', 'tenant_code');

-- Invitation / user-related
SELECT create_distributed_table('invitations','tenant_code');
Expand Down
17 changes: 4 additions & 13 deletions src/dtos/organizationDTO.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,29 +11,20 @@ class organizationDTO {
}
}

static eventBodyDTO({ entity, eventType, entityId, changedValues = [], args = {} }) {
static eventBodyDTO({ entity, eventType, entityId, oldValues, newValues, args = {} }) {
try {
if (!entity || !eventType || !entityId)
throw new Error('Entity, EventType & EntityId values are mandatory for an Event')

const disallowedArgs = ['deleted_at']
disallowedArgs.forEach((key) => delete args[key])

disallowedArgs.forEach((key) => {
delete args[key]
})

const changes = changedValues.reduce((result, obj) => {
const { fieldName, oldValue, newValue } = obj
if (!result[fieldName]) result[fieldName] = {}
if (oldValue) result[fieldName].oldValue = oldValue
if (newValue) result[fieldName].newValue = newValue
return result
}, {})
return {
entity,
eventType,
entityId,
changes,
...(oldValues ? { oldValues } : {}),
...(newValues ? { newValues } : {}),
...args,
}
} catch (error) {
Expand Down
12 changes: 3 additions & 9 deletions src/dtos/tenantDTO.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ class TenantResponseDTO {
}
}

static eventBodyDTO({ entity, eventType, entityId, changedValues = [], args = {} }) {
static eventBodyDTO({ entity, eventType, entityId, oldValues, newValues, args = {} }) {
try {
if (!entity || !eventType || !entityId)
throw new Error('Entity, EventType & EntityId values are mandatory for an Event')
Expand All @@ -31,18 +31,12 @@ class TenantResponseDTO {
delete args[key]
})

const changes = changedValues.reduce((result, obj) => {
const { fieldName, oldValue, newValue } = obj
if (!result[fieldName]) result[fieldName] = {}
if (oldValue) result[fieldName].oldValue = oldValue
if (newValue) result[fieldName].newValue = newValue
return result
}, {})
return {
entity,
eventType,
entityId,
changes,
...(oldValues ? { oldValues } : {}),
...(newValues ? { newValues } : {}),
...args,
}
} catch (error) {
Expand Down
24 changes: 11 additions & 13 deletions src/dtos/userDTO.js
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,8 @@ class UserDTO {
]
return input.filter((key) => !restrictedKeys.includes(key))
}
static eventBodyDTO({ entity, eventType, entityId, changedValues = [], args = {} }) {

static eventBodyDTO({ entity, eventType, entityId, oldValues = {}, newValues = {}, args = {} }) {
try {
if (!entity || !eventType || !entityId)
throw new Error('Entity, EventType & EntityId values are mandatory for an Event')
Expand All @@ -91,23 +92,20 @@ class UserDTO {
'custom_entity_text',
]

disallowedArgs.forEach((key) => {
delete args[key]
})
const sanitize = (obj) => {
if (obj && typeof obj === 'object') {
disallowedArgs.forEach((key) => delete obj[key])
}
return obj
}

const changes = changedValues.reduce((result, obj) => {
const { fieldName, oldValue, newValue } = obj
if (!result[fieldName]) result[fieldName] = {}
if (oldValue) result[fieldName].oldValue = oldValue
if (newValue) result[fieldName].newValue = newValue
return result
}, {})
return {
entity,
eventType,
entityId,
changes,
...args,
...(Object.keys(sanitize(oldValues)).length ? { oldValues } : {}),
...(Object.keys(sanitize(newValues)).length ? { newValues } : {}),
...sanitize(args),
}
} catch (error) {
console.error(error)
Expand Down
15 changes: 15 additions & 0 deletions src/envVariables.js
Original file line number Diff line number Diff line change
Expand Up @@ -458,6 +458,21 @@ let enviromentVariables = {
value: 'true',
},
},
ORG_ID_HEADER_NAME: {
message: 'Required ORG_ID_HEADER_NAME',
optional: true,
default: 'x-org-id',
},
ORG_CODE_HEADER_NAME: {
message: 'Required ORG_CODE_HEADER_NAME',
optional: true,
default: 'x-org-code',
},
TENANT_CODE_HEADER_NAME: {
message: 'Required TENANT_CODE_HEADER_NAME',
optional: true,
default: 'x-tenant-code',
},
}
let success = true

Expand Down
24 changes: 24 additions & 0 deletions src/generics/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -1112,6 +1112,29 @@ function extractUpdatedValues(oldData = {}, newData = {}, updateData = {}) {
return changes
}

function flattenLeafPaths(obj, prefix = '') {
const out = []
for (const [k, v] of Object.entries(obj || {})) {
const path = prefix ? `${prefix}.${k}` : k
if (v && typeof v === 'object' && !Array.isArray(v)) out.push(...flattenLeafPaths(v, path))
else out.push(path)
}
return out
}

function extractDelta(oldData = {}, newData = {}, updateData) {
const delta = {}
const paths =
updateData && Object.keys(updateData).length ? flattenLeafPaths(updateData) : flattenLeafPaths(newData) // fallback to full compare

for (const p of paths) {
const oldVal = _.get(oldData, p)
const newVal = _.get(newData, p)
if (!_.isEqual(oldVal, newVal)) _.set(delta, p, newVal)
}
return delta
}

module.exports = {
generateToken,
hashPassword,
Expand Down Expand Up @@ -1159,4 +1182,5 @@ module.exports = {
appendParamsToUrl,
parseMetaData,
extractUpdatedValues,
extractDelta,
}
4 changes: 4 additions & 0 deletions src/helpers/eventBroadcasterMain.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
'use strict'
const requester = require('@utils/requester')
const kafkaCommunication = require('@generics/kafka-communication')
const util = require('util')

const getEndpoints = (eventGroup) => {
switch (eventGroup) {
Expand Down Expand Up @@ -86,6 +87,9 @@ exports.eventBroadcasterKafka = async (eventGroup, { requestBody }) => {
}
exports.broadcastEvent = async (eventGroup, { requestBody, headers = {}, isInternal = true }) => {
try {
//TODO: Remove this log after testing
console.log(util.inspect(requestBody, { depth: null, colors: true, compact: false }))

// Fire both broadcaster functions concurrently
const broadcastPromises = [
exports.eventBroadcasterMain(eventGroup, { requestBody, headers, isInternal }),
Expand Down
12 changes: 11 additions & 1 deletion src/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -182,5 +182,15 @@
"REG_CODE_ERROR": "registration_code is not valid or unique.",
"INVALID_REG_CODE_ERROR": "registration_codes {{errorMessage}}. Invalid Code(s) : {{errorValues}}",
"UNIQUE_CONSTRAINT_ERROR": "{{fields}} is Invalid.",
"USER_PROFILE_FETCHED_SUCCESSFULLY": "User profile fetched successfully!"
"USER_PROFILE_FETCHED_SUCCESSFULLY": "User profile fetched successfully!",
"QUERY_EXECUTED_SUCCESSFULLY": "Raw query executed successfully!",
"INVALID_QUERY_INPUT": "Invalid query input provided.",
"ONLY_SELECT_QUERIES_ALLOWED": "Only SELECT queries are allowed.",
"QUERY_EXECUTION_FAILED": "Failed to execute query.",
"QUERY_INVALID_OR_UNBALANCED_QUOTES": "Query contains unbalanced or invalid quotes.",
"QUERY_FORBIDDEN_INJECTION_PATTERNS": "Query contains forbidden injection patterns.",
"QUERY_FORBIDDEN_PATTERNS": "Query contains forbidden SQL operations.",
"ADD_ORG_HEADER": "Please provide all required organization headers: {{orgCodeHeader}}, and {{tenantCodeHeader}} for admin override.",
"INVALID_ORG_ID": "Organization ID must be a valid positive integer.",
"INVALID_ORG_OR_TENANT_CODE": "The provided organization or tenant code is invalid or does not match."
}
Loading