Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
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
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
4 changes: 3 additions & 1 deletion src/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -182,5 +182,7 @@
"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: {orgIdHeader}, {orgCodeHeader}, and {tenantCodeHeader} for admin override.",
"INVALID_ORG_ID": "Organization ID must be a valid positive integer."
}
54 changes: 50 additions & 4 deletions src/middlewares/authenticator.js
Original file line number Diff line number Diff line change
Expand Up @@ -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')
Expand Down Expand Up @@ -159,6 +159,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)
Expand Down Expand Up @@ -210,10 +211,55 @@ module.exports = async function (req, res, next) {
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()
}

if (isAdmin) {
// For admin users, allow overriding tenant_code, organization_id, and organization_code via headers
// Header names are configurable via environment variables with sensible defaults
const orgIdHeaderName = process.env.ORG_ID_HEADER_NAME
const orgCodeHeaderName = process.env.ORG_CODE_HEADER_NAME
const tenantCodeHeaderName = process.env.TENANT_CODE_HEADER_NAME

// Extract and sanitize header values (trim whitespace, case-insensitive header lookup)
const orgId = (req.headers[orgIdHeaderName.toLowerCase()] || '').trim()
const orgCode = (req.headers[orgCodeHeaderName.toLowerCase()] || '').trim()
const tenantCode = (req.headers[tenantCodeHeaderName.toLowerCase()] || '').trim()

Comment on lines +219 to +227
Copy link

@coderabbitai coderabbitai bot Oct 9, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Avoid toLowerCase on potentially undefined env vars; prefer req.get and add safe defaults.

If env is unset, orgIdHeaderName.toLowerCase() throws. Also use req.get() for case-insensitive retrieval.

-			const orgIdHeaderName = process.env.ORG_ID_HEADER_NAME
-			const orgCodeHeaderName = process.env.ORG_CODE_HEADER_NAME
-			const tenantCodeHeaderName = process.env.TENANT_CODE_HEADER_NAME
-
-			// Extract and sanitize header values (trim whitespace, case-insensitive header lookup)
-			const orgId = (req.headers[orgIdHeaderName.toLowerCase()] || '').trim()
-			const orgCode = (req.headers[orgCodeHeaderName.toLowerCase()] || '').trim()
-			const tenantCode = (req.headers[tenantCodeHeaderName.toLowerCase()] || '').trim()
+			const orgIdHeaderName = process.env.ORG_ID_HEADER_NAME || 'X-Org-Id'
+			const orgCodeHeaderName = process.env.ORG_CODE_HEADER_NAME || 'X-Org-Code'
+			const tenantCodeHeaderName = process.env.TENANT_CODE_HEADER_NAME || 'X-Tenant-Code'
+
+			// Extract and sanitize header values (Express req.get is case-insensitive)
+			const orgId = (req.get(orgIdHeaderName) || '').trim()
+			const orgCode = (req.get(orgCodeHeaderName) || '').trim()
+			const tenantCode = (req.get(tenantCodeHeaderName) || '').trim()
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const orgIdHeaderName = process.env.ORG_ID_HEADER_NAME
const orgCodeHeaderName = process.env.ORG_CODE_HEADER_NAME
const tenantCodeHeaderName = process.env.TENANT_CODE_HEADER_NAME
// Extract and sanitize header values (trim whitespace, case-insensitive header lookup)
const orgId = (req.headers[orgIdHeaderName.toLowerCase()] || '').trim()
const orgCode = (req.headers[orgCodeHeaderName.toLowerCase()] || '').trim()
const tenantCode = (req.headers[tenantCodeHeaderName.toLowerCase()] || '').trim()
const orgIdHeaderName = process.env.ORG_ID_HEADER_NAME || 'X-Org-Id'
const orgCodeHeaderName = process.env.ORG_CODE_HEADER_NAME || 'X-Org-Code'
const tenantCodeHeaderName = process.env.TENANT_CODE_HEADER_NAME || 'X-Tenant-Code'
// Extract and sanitize header values (Express req.get is case-insensitive)
const orgId = (req.get(orgIdHeaderName) || '').trim()
const orgCode = (req.get(orgCodeHeaderName) || '').trim()
const tenantCode = (req.get(tenantCodeHeaderName) || '').trim()
🤖 Prompt for AI Agents
In src/middlewares/authenticator.js around lines 219 to 227, the current code
calls toLowerCase() on environment variables and indexes req.headers directly,
which will throw if an env var is undefined and isn't the safest way to do
case-insensitive header lookup; change the code to first default each header
name from process.env to a safe string (e.g. '' or a known header key) before
calling toLowerCase or avoid calling it entirely, and use Express's
req.get(headerName) to retrieve headers case-insensitively, then trim the
returned value or default to '' so orgId, orgCode and tenantCode are always
defined strings.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We have already added a default values for these new env variables.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check the implementation of envVariables.js to understand how defaults are applied
cat src/envVariables.js

Length of output: 15049


🏁 Script executed:

#!/bin/bash
# Check if authenticator.js imports or uses the envVariables module
rg -n "envVariables|@envVariables|require.*envVariables" src/middlewares/authenticator.js

Length of output: 0


🏁 Script executed:

#!/bin/bash
# Check how envVariables is typically used in other parts of the codebase
rg -n -C2 "require.*envVariables" --type=js -g '!node_modules/**' | head -50

Length of output: 148


🏁 Script executed:

#!/bin/bash
# Find the application entry point files
fd -e js "^(app|server|index)\.js$" -d 2

Length of output: 10


🏁 Script executed:

#!/bin/bash
# Check if envVariables is imported/required in common entry points
rg -n "envVariables" --type=js src/app.js src/server.js src/index.js app.js server.js index.js 2>/dev/null || echo "No matches found in entry points"

Length of output: 96


🏁 Script executed:

#!/bin/bash
# Search more broadly for where envVariables module is used/invoked
rg -n "require.*envVariables|envVariables\(\)" --type=js -g '!node_modules' -g '!dist' -g '!build'

Length of output: 148


🏁 Script executed:

#!/bin/bash
# Check the startup sequence in app.js to confirm envVariables runs before middleware
cat src/app.js | head -30

Length of output: 934


@nevil-mathew, you're absolutely right! I verified that src/app.js line 22 executes require('./envVariables')() at application startup, which sets these environment variables to their defaults before any middleware loads:

ORG_ID_HEADER_NAME: 'X-Org-Id'
ORG_CODE_HEADER_NAME: 'X-Org-Code'  
TENANT_CODE_HEADER_NAME: 'X-Tenant-Code'

So toLowerCase() won't throw errors as I initially warned. The current implementation is safe.

That said, using req.get(headerName) instead of req.headers[headerName.toLowerCase()] is still considered better practice since it's the Express-recommended way for case-insensitive header retrieval, but it's not critical given your startup sequence guarantees these values exist. The current code will work correctly.


🧬 Code Graph Analysis Results

[src/services/organization.js] lines 518-521

let filter = {
				organization_code: { [Op.in]: [organizationCode, defaultOrganizationCode] },
				tenant_code: tenantCode,
			}

🧠 Learnings used
Learnt from: nevil-mathew
PR: ELEVATE-Project/user#776
File: src/services/entities.js:18-23
Timestamp: 2025-07-31T08:44:36.982Z
Learning: In the ELEVATE-Project/user codebase, organizationCode and tenantCode parameters passed to service methods always come from req.decodedToken.organization_code and req.decodedToken.tenant_code, which are guaranteed to be present after token validation. Additional validation for these parameters in service methods is unnecessary as the token validation process ensures they are always available.

// If any override header is provided (non-empty after trim), all three must be present and non-empty
const hasAnyOverrideHeader = orgId || orgCode || tenantCode
if (hasAnyOverrideHeader) {
if (!orgId || !orgCode || !tenantCode) {
throw responses.failureResponse({
message: {
key: 'ADD_ORG_HEADER',
interpolation: {
orgIdHeader: orgIdHeaderName,
orgCodeHeader: orgCodeHeaderName,
tenantCodeHeader: tenantCodeHeaderName,
},
},
statusCode: httpStatusCode.bad_request,
responseCode: 'CLIENT_ERROR',
})
}

// Validate orgId is a valid positive integer
const parsedOrgId = parseInt(orgId, 10)
if (isNaN(parsedOrgId) || parsedOrgId <= 0) {
throw responses.failureResponse({
message: 'INVALID_ORG_ID',
statusCode: httpStatusCode.bad_request,
responseCode: 'CLIENT_ERROR',
})
}

// Override the values from the token with sanitized header values
decodedToken.data.tenant_code = tenantCode
decodedToken.data.organization_id = orgId
decodedToken.data.organization_code = orgCode
}

req.decodedToken = decodedToken.data
}

if (roleValidation) {
Expand Down