diff --git a/src/constants/common.js b/src/constants/common.js index 37791b56..68580fbe 100644 --- a/src/constants/common.js +++ b/src/constants/common.js @@ -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), diff --git a/src/controllers/v1/admin.js b/src/controllers/v1/admin.js index b18b4c3f..6ca70e08 100644 --- a/src/controllers/v1/admin.js +++ b/src/controllers/v1/admin.js @@ -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 @@ -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 diff --git a/src/controllers/v1/form.js b/src/controllers/v1/form.js index c0c0ba0e..31becc9b 100644 --- a/src/controllers/v1/form.js +++ b/src/controllers/v1/form.js @@ -7,6 +7,7 @@ // Dependencies const formsService = require('@services/form') +const common = require('@constants/common') module.exports = class Form { /** @@ -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 diff --git a/src/controllers/v1/public.js b/src/controllers/v1/public.js index adf53139..5df953a7 100644 --- a/src/controllers/v1/public.js +++ b/src/controllers/v1/public.js @@ -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) } diff --git a/src/controllers/v1/tenant.js b/src/controllers/v1/tenant.js index 8f9822bc..0e628ed7 100644 --- a/src/controllers/v1/tenant.js +++ b/src/controllers/v1/tenant.js @@ -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() ) diff --git a/src/envVariables.js b/src/envVariables.js index e640e1c0..692b6dba 100644 --- a/src/envVariables.js +++ b/src/envVariables.js @@ -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 diff --git a/src/locales/en.json b/src/locales/en.json index 64d5f726..9bb0110b 100644 --- a/src/locales/en.json +++ b/src/locales/en.json @@ -182,5 +182,8 @@ "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!", + "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." } diff --git a/src/middlewares/authenticator.js b/src/middlewares/authenticator.js index 5ce40ccc..a6daab70 100644 --- a/src/middlewares/authenticator.js +++ b/src/middlewares/authenticator.js @@ -10,7 +10,7 @@ const jwt = require('jsonwebtoken') const httpStatusCode = require('@generics/http-status') const common = require('@constants/common') const userQueries = require('@database/queries/users') -const roleQueries = require('@database/queries/user-role') + const rolePermissionMappingQueries = require('@database/queries/role-permission-mapping') const { Op } = require('sequelize') const responses = require('@helpers/responses') @@ -18,6 +18,7 @@ const utilsHelper = require('@generics/utils') const { verifyCaptchaToken } = require('@utils/captcha') const { getDomainFromRequest } = require('@utils/domain') const tenantDomainQueries = require('@database/queries/tenantDomain') +const organizationQueries = require('@database/queries/organization') async function checkPermissions(roleTitle, requestPath, requestMethod) { const parts = requestPath.match(/[^/]+/g) @@ -110,18 +111,13 @@ module.exports = async function (req, res, next) { }) } - const domain = getDomainFromRequest(req) || null - const tenant_code = - req?.headers?.tenantId || - req?.headers?.tenantid || - req?.headers?.tenant_Id || - req?.headers?.tenant_id || - req?.headers?.tenant || - req?.headers?.tenant_code || - req.headers.tenantCode || - null + const tenantFilter = {} + const domain = getDomainFromRequest(req) + + const tenant_code = req?.headers?.[common.TENANT_CODE_HEADER] ?? null - const tenantFilter = domain ? { domain } : tenant_code ? { tenant_code } : null || {} + if (domain) tenantFilter.domain = domain + else if (tenant_code) tenantFilter.tenant_code = tenant_code if (Object.keys(tenantFilter).length > 0) { const tenantDomain = await tenantDomainQueries.findOne(tenantFilter, { @@ -159,6 +155,7 @@ module.exports = async function (req, res, next) { token = extractedToken.trim() } else token = authHeader.trim() + let decodedToken let org try { decodedToken = jwt.verify(token, process.env.ACCESS_TOKEN_SECRET) @@ -209,10 +206,56 @@ module.exports = async function (req, res, next) { //check for admin user let isAdmin = false if (decodedToken.data.roles) { - isAdmin = decodedToken.data.roles.some((role) => role.title == common.ADMIN_ROLE) - if (isAdmin) { - req.decodedToken = decodedToken.data - return next() + isAdmin = decodedToken.data.roles.some((role) => role.title === common.ADMIN_ROLE) + } + + if (isAdmin) { + // For admin users, allow overriding tenant_code and organization_code via headers + // Header names are configurable via environment variables with sensible defaults + const orgCodeHeaderName = common.ORG_CODE_HEADER + const tenantCodeHeaderName = common.TENANT_CODE_HEADER + + // Extract and sanitize header values (trim whitespace, case-insensitive header lookup) + const orgCode = (req.headers[orgCodeHeaderName.toLowerCase()] || '').trim() + const tenantCode = (req.headers[tenantCodeHeaderName.toLowerCase()] || '').trim() + + // If any override header is provided (non-empty after trim), both must be present and non-empty + const hasAnyOverrideHeader = orgCode || tenantCode + if (hasAnyOverrideHeader) { + if (!orgCode || !tenantCode) { + throw responses.failureResponse({ + message: { + key: 'ADD_ORG_HEADER', + interpolation: { + orgCodeHeader: orgCodeHeaderName, + tenantCodeHeader: tenantCodeHeaderName, + }, + }, + statusCode: httpStatusCode.bad_request, + responseCode: 'CLIENT_ERROR', + }) + } + + // Query the database to find the organization based on orgCode and tenantCode + const org = await organizationQueries.findOne({ + code: orgCode, + tenant_code: tenantCode, + status: common.ACTIVE_STATUS, + deleted_at: null, + }) + + if (!org) { + throw responses.failureResponse({ + message: 'INVALID_ORG_OR_TENANT_CODE', + statusCode: httpStatusCode.bad_request, + responseCode: 'CLIENT_ERROR', + }) + } + + // Override the values from the token with sanitized header values and fetched orgId + decodedToken.data.tenant_code = tenantCode + decodedToken.data.organization_id = org.id // Use the ID from the database + decodedToken.data.organization_code = orgCode } }