Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
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
3 changes: 3 additions & 0 deletions src/.env.sample
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,9 @@ 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

#database url
DATABASE_URL=postgres://postgres:postgres@localhost:5432/elevate-user

1 change: 0 additions & 1 deletion src/constants/blacklistConfig.js
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,6 @@ const account = {
registrationOtp: [
'id',
'email_verified',
'name',
'gender',
'location',
'about',
Expand Down
2 changes: 1 addition & 1 deletion src/constants/common.js
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
20 changes: 18 additions & 2 deletions src/envVariables.js
Original file line number Diff line number Diff line change
Expand Up @@ -100,11 +100,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: {
Expand Down Expand Up @@ -241,6 +241,22 @@ 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',
},
DOWNLOAD_URL_EXPIRATION_DURATION: {
message: 'Required downloadable url expiration time',
optional: true,
default: 3600000,
},
}

let success = true
Expand Down
3 changes: 2 additions & 1 deletion src/generics/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
4 changes: 3 additions & 1 deletion src/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"

}
5 changes: 4 additions & 1 deletion src/middlewares/validator.js
Original file line number Diff line number Diff line change
Expand Up @@ -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()
}
2 changes: 1 addition & 1 deletion src/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
89 changes: 83 additions & 6 deletions src/routes/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,94 @@ 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
allowedControllers.push('cloud-services')
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 {
Expand Down Expand Up @@ -53,16 +132,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)
Expand Down
13 changes: 13 additions & 0 deletions src/services/org-admin.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 },
Expand Down
16 changes: 14 additions & 2 deletions src/validators/v1/account.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,13 @@ 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(process.env.PASSWORD_POLICY_REGEX)
.withMessage(process.env.PASSWORD_POLICY_MESSAGE)
.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")
Expand Down Expand Up @@ -73,7 +79,13 @@ module.exports = {
resetPassword: (req) => {
req.body = filterRequestBody(req.body, account.resetPassword)
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(process.env.PASSWORD_POLICY_REGEX)
.withMessage(process.env.PASSWORD_POLICY_MESSAGE)
.custom((value) => !/\s/.test(value))
.withMessage('Password cannot contain spaces')

req.checkBody('otp')
.notEmpty()
Expand Down
8 changes: 7 additions & 1 deletion src/validators/v1/admin.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,13 @@ 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(process.env.PASSWORD_POLICY_REGEX)
.withMessage(process.env.PASSWORD_POLICY_MESSAGE)
.custom((value) => !/\s/.test(value))
.withMessage('Password cannot contain spaces')
},

login: (req) => {
Expand Down
2 changes: 2 additions & 0 deletions src/validators/v1/organization.js
Original file line number Diff line number Diff line change
Expand Up @@ -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')
Expand All @@ -42,6 +43,7 @@ module.exports = {
.trim()
.notEmpty()
.withMessage('description field is empty')
.not()
.matches(/(\b)(on\S+)(\s*)=|javascript:|<(|\/|[^\/>][^>]+|\/[^>][^>]+)>/gi)
.withMessage('invalid description')
},
Expand Down
1 change: 1 addition & 0 deletions src/validators/v1/user.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ module.exports = {
.trim()
.notEmpty()
.withMessage('about field is empty')
.not()
.matches(/(\b)(on\S+)(\s*)=|javascript:|<(|\/|[^\/>][^>]+|\/[^>][^>]+)>/gi)
.withMessage('invalid about')

Expand Down