Skip to content
Merged
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
47 changes: 47 additions & 0 deletions src/api-doc/MentorED-Users.postman_collection.json
Original file line number Diff line number Diff line change
Expand Up @@ -450,6 +450,53 @@
}
]
},

{
"name": "Change Password",
"request": {
"method": "POST",
"header": [
{
"key": "X-auth-token",
"value": "bearer {{token}}",
"type": "text"
}
],
"body": {
"mode": "urlencoded",
"urlencoded": [
{
"key": "oldPassword",
"value": "password",
"type": "text"
},
{
"key": "newPassword",
"value": "Password@123",
"type": "text"
}
]
},
"url": {
"raw": "{{UserDevBaseUrl}}user/v1/account/changePassword",
"host": ["{{UserDevBaseUrl}}user"],
"path": ["v1", "account", "changePassword"],
"query": [
{
"key": "oldPassword",
"value": "password",
"disabled": true
},
{
"key": "newPassword",
"value": "Password@123",
"disabled": true
}
]
}
},
"response": []
},
{
"name": "User",
"item": [
Expand Down
22 changes: 22 additions & 0 deletions src/controllers/v1/account.js
Original file line number Diff line number Diff line change
Expand Up @@ -244,4 +244,26 @@ module.exports = class Account {
return error
}
}

/**
* change password
* @method
* @name changePassword
* @param {Object} req -request data.
* @param {Object} req.decodedToken.id - UserId.
* @param {string} req.body - request body contains user password
* @param {string} req.body.OldPassword - user Old Password.
* @param {string} req.body.NewPassword - user New Password.
* @param {string} req.body.ConfirmNewPassword - user Confirming New Password.
* @returns {JSON} - password changed response
*/

async changePassword(req) {
try {
const result = await accountService.changePassword(req.body, req.decodedToken.id)
return result
} catch (error) {
return error
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
const moment = require('moment')

module.exports = {
up: async (queryInterface, Sequelize) => {
const defaultOrgId = queryInterface.sequelize.options.defaultOrgId
if (!defaultOrgId) {
throw new Error('Default org ID is undefined. Please make sure it is set in sequelize options.')
}
const emailTemplates = [
{
code: 'change_password',
subject: 'Password Change',
body: '<p>Dear {name},</p> Your password has been changed successfully.',
},
]

let notificationTemplateData = []
emailTemplates.forEach(async function (emailTemplate) {
emailTemplate['status'] = 'ACTIVE'
emailTemplate['type'] = 'email'
emailTemplate['updated_at'] = moment().format()
emailTemplate['created_at'] = moment().format()
emailTemplate['organization_id'] = defaultOrgId
emailTemplate['email_footer'] = 'email_footer'
emailTemplate['email_header'] = 'email_header'

notificationTemplateData.push(emailTemplate)
})

await queryInterface.bulkInsert('notification_templates', notificationTemplateData, {})
},

down: async (queryInterface, Sequelize) => {
await queryInterface.bulkDelete('notification_templates', null, {})
},
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
'use strict'

/** @type {import('sequelize-cli').Migration} */
module.exports = {
async up(queryInterface, Sequelize) {
try {
const permissionsData = [
{
code: 'change_password_update',
module: 'account',
request_type: ['POST'],
api_path: '/user/v1/account/changePassword',
status: 'ACTIVE',
},
]

// Batch insert permissions
await queryInterface.bulkInsert(
'permissions',
permissionsData.map((permission) => ({
...permission,
created_at: new Date(),
updated_at: new Date(),
}))
)
} catch (error) {
console.error('Error in migration:', error)
throw error
}
},

async down(queryInterface, Sequelize) {
try {
// Rollback migration by deleting all permissions
await queryInterface.bulkDelete('permissions', null, {})
} catch (error) {
console.error('Error in rollback migration:', error)
throw error
}
},
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
'use strict'

require('module-alias/register')
require('dotenv').config()
const common = require('@constants/common')
const Permissions = require('@database/models/index').Permission

const getPermissionId = async (module, request_type, api_path) => {
try {
const permission = await Permissions.findOne({
where: { module, request_type, api_path },
})
if (!permission) {
throw new Error(
`Permission not found for module: ${module}, request_type: ${request_type}, api_path: ${api_path}`
)
}
return permission.id
} catch (error) {
throw new Error(`Error while fetching permission: ${error.message}`)
}
}

module.exports = {
up: async (queryInterface, Sequelize) => {
try {
const rolePermissionsData = await Promise.all([
{
role_title: common.MENTOR_ROLE,
permission_id: await getPermissionId('account', ['POST'], '/user/v1/account/changePassword'),
module: 'account',
request_type: ['POST'],
api_path: '/user/v1/account/changePassword',
},
{
role_title: common.MENTEE_ROLE,
permission_id: await getPermissionId('account', ['POST'], '/user/v1/account/changePassword'),
module: 'account',
request_type: ['POST'],
api_path: '/user/v1/account/changePassword',
},
{
role_title: common.ORG_ADMIN_ROLE,
permission_id: await getPermissionId('account', ['POST'], '/user/v1/account/changePassword'),
module: 'account',
request_type: ['POST'],
api_path: '/user/v1/account/changePassword',
},
{
role_title: common.USER_ROLE,
permission_id: await getPermissionId('account', ['POST'], '/user/v1/account/changePassword'),
module: 'account',
request_type: ['POST'],
api_path: '/user/v1/account/changePassword',
},
{
role_title: common.ADMIN_ROLE,
permission_id: await getPermissionId('account', ['POST'], '/user/v1/account/changePassword'),
module: 'account',
request_type: ['POST'],
api_path: '/user/v1/account/changePassword',
},
{
role_title: common.SESSION_MANAGER_ROLE,
permission_id: await getPermissionId('account', ['POST'], '/user/v1/account/changePassword'),
module: 'account',
request_type: ['POST'],
api_path: '/user/v1/account/changePassword',
},
])

await queryInterface.bulkInsert(
'role_permission_mapping',
rolePermissionsData.map((data) => ({
...data,
created_at: new Date(),
updated_at: new Date(),
created_by: 0,
}))
)
} catch (error) {
console.log(error)
console.error(`Migration error: ${error.message}`)
throw error
}
},

down: async (queryInterface, Sequelize) => {
try {
await queryInterface.bulkDelete('role_permission_mapping', null, {})
} catch (error) {
console.error(`Rollback migration error: ${error.message}`)
throw error
}
},
}
5 changes: 2 additions & 3 deletions src/envVariables.js
Original file line number Diff line number Diff line change
Expand Up @@ -244,13 +244,12 @@ let enviromentVariables = {
PASSWORD_POLICY_REGEX: {
message: 'Required password policy',
optional: true,
default: '^(?=.*?[A-Z])(?=.*?[a-z])(?=.*?[0-9])(?=.*?[#?!@$%^&*-]).{10,}$',
default: '^.{8,}$',
},
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',
default: 'Password must have at least 8 characters long',
},
DOWNLOAD_URL_EXPIRATION_DURATION: {
message: 'Required downloadable url expiration time',
Expand Down
6 changes: 4 additions & 2 deletions src/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,8 @@
"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.",
"INAVLID_ORG_ROLE_REQ": "Invalid organisation request"

"INAVLID_ORG_ROLE_REQ": "Invalid organisation request",
"INCORRECT_OLD_PASSWORD": "Invalid old password",
"SAME_PASSWORD_ERROR": "New password cannot be same as old password",
"PASSWORD_CHANGED_SUCCESSFULLY": "Password changed successfully."
}
97 changes: 97 additions & 0 deletions src/services/account.js
Original file line number Diff line number Diff line change
Expand Up @@ -1253,4 +1253,101 @@ module.exports = class AccountHelper {
throw error
}
}

/**
* change password
* @method
* @name changePassword
* @param {Object} req -request data.
* @param {Object} req.decodedToken.id - UserId.
* @param {string} req.body - request body contains user password
* @param {string} req.body.OldPassword - user Old Password.
* @param {string} req.body.NewPassword - user New Password.
* @param {string} req.body.ConfirmNewPassword - user Confirming New Password.
* @returns {JSON} - password changed response
*/

static async changePassword(bodyData, userId) {
const projection = ['location']
try {
const userCredentials = await UserCredentialQueries.findOne({ user_id: userId })
if (!userCredentials) {
return responses.failureResponse({
message: 'USER_DOESNOT_EXISTS',
statusCode: httpStatusCode.bad_request,
responseCode: 'CLIENT_ERROR',
})
}
const plaintextEmailId = emailEncryption.decrypt(userCredentials.email)

let user = await userQueries.findOne(
{ id: userCredentials.user_id, organization_id: userCredentials.organization_id },
{ attributes: { exclude: projection } }
)
if (!user) {
return responses.failureResponse({
message: 'USER_DOESNOT_EXISTS',
statusCode: httpStatusCode.bad_request,
responseCode: 'CLIENT_ERROR',
})
}

const verifyOldPassword = utilsHelper.comparePassword(bodyData.oldPassword, userCredentials.password)
if (!verifyOldPassword) {
return responses.failureResponse({
message: 'INCORRECT_OLD_PASSWORD',
statusCode: httpStatusCode.bad_request,
responseCode: 'CLIENT_ERROR',
})
}

const isPasswordSame = bcryptJs.compareSync(bodyData.newPassword, userCredentials.password)
if (isPasswordSame) {
return responses.failureResponse({
message: 'SAME_PASSWORD_ERROR',
statusCode: httpStatusCode.bad_request,
responseCode: 'CLIENT_ERROR',
})
}
bodyData.newPassword = utilsHelper.hashPassword(bodyData.newPassword)

const updateParams = { password: bodyData.newPassword }

await userQueries.updateUser(
{ id: user.id, organization_id: userCredentials.organization_id },
updateParams
)
await UserCredentialQueries.updateUser({ email: userCredentials.email }, { password: bodyData.newPassword })
await utilsHelper.redisDel(userCredentials.email)

const templateData = await notificationTemplateQueries.findOneEmailTemplate(
process.env.CHANGE_PASSWORD_TEMPLATE_CODE
)

if (templateData) {
// Push successful registration email to kafka
const payload = {
type: common.notificationEmailType,
email: {
to: plaintextEmailId,
subject: templateData.subject,
body: utilsHelper.composeEmailBody(templateData.body, {
name: user.name,
}),
},
}
await kafkaCommunication.pushEmailToKafka(payload)
}

const result = {}
return responses.successResponse({
statusCode: httpStatusCode.ok,
message: 'PASSWORD_CHANGED_SUCCESSFULLY',
result,
})
} catch (error) {
console.log(error)
throw error
}
}
}