From 13dbe75edec69c104514508ec41c1a1c57cc819c Mon Sep 17 00:00:00 2001 From: yashwanth170 Date: Sat, 9 Nov 2024 11:51:52 -0500 Subject: [PATCH 1/4] Feature: Backend for permission log table --- .../logUserPermissionController.js | 82 +++++++++++++++++++ src/models/userPermissionChangeLog.js | 23 ++++++ src/routes/userProfileRouter.js | 5 ++ 3 files changed, 110 insertions(+) create mode 100644 src/controllers/logUserPermissionController.js create mode 100644 src/models/userPermissionChangeLog.js diff --git a/src/controllers/logUserPermissionController.js b/src/controllers/logUserPermissionController.js new file mode 100644 index 000000000..8c6a3b218 --- /dev/null +++ b/src/controllers/logUserPermissionController.js @@ -0,0 +1,82 @@ +const moment = require('moment-timezone'); +const UserProfile = require('../models/userProfile'); +const UserPermissionChangeLog = require('../models/userPermissionChangeLog'); +const PermissionChangeLog = require('../models/permissionChangeLog'); + +const logUserPermissionController = function () { + const logPermissionChanges = async function (req, res) { + try { + const { + actualUserProfile, + authUser, + userId, + existingPermissions, + addedPermissions, + removedPermissions, + } = req.body; + + const dateTime = moment().tz('America/Los_Angeles').format(); + + const logEntry = new UserPermissionChangeLog({ + logDateTime: dateTime, + userId, + individualName: `${actualUserProfile.firstName} ${actualUserProfile.lastName}`, + permissions: existingPermissions, + permissionsAdded: addedPermissions, + permissionsRemoved: removedPermissions, + requestorRole: authUser.role, + requestorEmail: authUser.email, + }); + + await logEntry.save(); + res.status(200).json({ message: 'Permission changes logged successfully' }); + } catch (error) { + console.error('Error logging permission change:', error); + res.status(500).json({ error: 'Failed to log permission change' }); + } + }; + + const getPermissionChangeLogs = async function (req, res) { + try { + const userProfile = await UserProfile.findOne({ _id: req.params.userId }).exec(); + + if (userProfile) { + if (userProfile.role !== 'Owner') { + res.status(204).send([]); + } else { + // Fetch logs from both collections + const userChangeLogs = await UserPermissionChangeLog.find(); + const rolePermissionChangeLogs = await PermissionChangeLog.find(); + + const formattedUserChangeLogs = userChangeLogs.map(log => ({ + ...log.toObject(), + name: log.individualName, + })); + + const formattedRolePermissionChangeLogs = rolePermissionChangeLogs.map(log => ({ + ...log.toObject(), + name: log.roleName, + })); + + const mergedLogs = [...formattedUserChangeLogs, ...formattedRolePermissionChangeLogs].sort( + (a, b) => new Date(b.logDateTime) - new Date(a.logDateTime) + ); + + res.status(200).json(mergedLogs); + } + } else { + res.status(403).send(`User (${req.params.userId}) not found.`); + } + } catch (error) { + console.error('Error fetching permission change logs:', error); + res.status(500).json({ error: 'Failed to fetch permission change logs' }); + } + }; + + return { + logPermissionChanges, + getPermissionChangeLogs, + }; +}; + +module.exports = logUserPermissionController; diff --git a/src/models/userPermissionChangeLog.js b/src/models/userPermissionChangeLog.js new file mode 100644 index 000000000..051a597dc --- /dev/null +++ b/src/models/userPermissionChangeLog.js @@ -0,0 +1,23 @@ +const mongoose = require('mongoose'); + +const { Schema } = mongoose; + +const User = require('./userProfile'); + + +const UserPermissionChangeLog = new Schema({ + logDateTime: { type: String, required: true }, + userId: { + type: mongoose.Types.ObjectId, + ref: User, + required: true, + }, + individualName: { type: String }, + permissions: { type: [String], required: true }, + permissionsAdded: { type: [String], default: [] }, + permissionsRemoved: { type: [String], default: [] }, + requestorRole: { type: String }, + requestorEmail: { type: String, required: true }, +}); + +module.exports = mongoose.model('UserPermissionChangeLog', UserPermissionChangeLog, 'UserPermissionChangeLogs'); diff --git a/src/routes/userProfileRouter.js b/src/routes/userProfileRouter.js index 73bc2ac5b..5245738ea 100644 --- a/src/routes/userProfileRouter.js +++ b/src/routes/userProfileRouter.js @@ -2,6 +2,7 @@ const { body } = require('express-validator'); const express = require('express'); const { ValidationError } = require('../utilities/errorHandling/customError'); +const logUserPermissionController = require('../controllers/logUserPermissionController')(); const routes = function (userProfile, project) { const controller = require('../controllers/userProfileController')(userProfile, project); @@ -116,6 +117,10 @@ const routes = function (userProfile, project) { userProfileRouter.route('/userProfile/teamCode/list').get(controller.getAllTeamCode); + userProfileRouter.post('/logPermissionChanges', logUserPermissionController.logPermissionChanges); + userProfileRouter.get('/logPermissionChanges/:userId', logUserPermissionController.getPermissionChangeLogs); + + return userProfileRouter; }; From a97d9c805cf1ef7a5c03ccbca5cac56d999a8720 Mon Sep 17 00:00:00 2001 From: yashwanth170 Date: Wed, 20 Nov 2024 18:18:26 -0500 Subject: [PATCH 2/4] Updated code with suggested changes --- .../logUserPermissionController.js | 82 ------------------- .../permissionChangeLogsController.js | 22 ++++- src/routes/permissionChangeLogsRouter.js | 4 +- src/routes/userProfileRouter.js | 3 +- src/startup/routes.js | 3 +- .../logUserPermissionChangeByAccount.js | 75 +++++++++++++++++ 6 files changed, 100 insertions(+), 89 deletions(-) delete mode 100644 src/controllers/logUserPermissionController.js create mode 100644 src/utilities/logUserPermissionChangeByAccount.js diff --git a/src/controllers/logUserPermissionController.js b/src/controllers/logUserPermissionController.js deleted file mode 100644 index 8c6a3b218..000000000 --- a/src/controllers/logUserPermissionController.js +++ /dev/null @@ -1,82 +0,0 @@ -const moment = require('moment-timezone'); -const UserProfile = require('../models/userProfile'); -const UserPermissionChangeLog = require('../models/userPermissionChangeLog'); -const PermissionChangeLog = require('../models/permissionChangeLog'); - -const logUserPermissionController = function () { - const logPermissionChanges = async function (req, res) { - try { - const { - actualUserProfile, - authUser, - userId, - existingPermissions, - addedPermissions, - removedPermissions, - } = req.body; - - const dateTime = moment().tz('America/Los_Angeles').format(); - - const logEntry = new UserPermissionChangeLog({ - logDateTime: dateTime, - userId, - individualName: `${actualUserProfile.firstName} ${actualUserProfile.lastName}`, - permissions: existingPermissions, - permissionsAdded: addedPermissions, - permissionsRemoved: removedPermissions, - requestorRole: authUser.role, - requestorEmail: authUser.email, - }); - - await logEntry.save(); - res.status(200).json({ message: 'Permission changes logged successfully' }); - } catch (error) { - console.error('Error logging permission change:', error); - res.status(500).json({ error: 'Failed to log permission change' }); - } - }; - - const getPermissionChangeLogs = async function (req, res) { - try { - const userProfile = await UserProfile.findOne({ _id: req.params.userId }).exec(); - - if (userProfile) { - if (userProfile.role !== 'Owner') { - res.status(204).send([]); - } else { - // Fetch logs from both collections - const userChangeLogs = await UserPermissionChangeLog.find(); - const rolePermissionChangeLogs = await PermissionChangeLog.find(); - - const formattedUserChangeLogs = userChangeLogs.map(log => ({ - ...log.toObject(), - name: log.individualName, - })); - - const formattedRolePermissionChangeLogs = rolePermissionChangeLogs.map(log => ({ - ...log.toObject(), - name: log.roleName, - })); - - const mergedLogs = [...formattedUserChangeLogs, ...formattedRolePermissionChangeLogs].sort( - (a, b) => new Date(b.logDateTime) - new Date(a.logDateTime) - ); - - res.status(200).json(mergedLogs); - } - } else { - res.status(403).send(`User (${req.params.userId}) not found.`); - } - } catch (error) { - console.error('Error fetching permission change logs:', error); - res.status(500).json({ error: 'Failed to fetch permission change logs' }); - } - }; - - return { - logPermissionChanges, - getPermissionChangeLogs, - }; -}; - -module.exports = logUserPermissionController; diff --git a/src/controllers/permissionChangeLogsController.js b/src/controllers/permissionChangeLogsController.js index 33ddb5d72..36e8486d4 100644 --- a/src/controllers/permissionChangeLogsController.js +++ b/src/controllers/permissionChangeLogsController.js @@ -1,6 +1,6 @@ const UserProfile = require('../models/userProfile'); -const permissionChangeLogController = function (PermissionChangeLog) { +const permissionChangeLogController = function (PermissionChangeLog,userPermissionChangeLog) { const getPermissionChangeLogs = async function (req, res) { try { const userProfile = await UserProfile.findOne({ _id: req.params.userId }).exec(); @@ -9,8 +9,24 @@ const permissionChangeLogController = function (PermissionChangeLog) { if (userProfile.role !== 'Owner') { res.status(204).send([]); } else { - const changeLogs = await PermissionChangeLog.find({}); - res.status(200).send(changeLogs); + const userChangeLogs = await userPermissionChangeLog.find(); + const rolePermissionChangeLogs = await PermissionChangeLog.find(); + + const formattedUserChangeLogs = userChangeLogs.map(log => ({ + ...log.toObject(), + name: log.individualName, + })); + + const formattedRolePermissionChangeLogs = rolePermissionChangeLogs.map(log => ({ + ...log.toObject(), + name: log.roleName, + })); + + const mergedLogs = [...formattedUserChangeLogs, ...formattedRolePermissionChangeLogs].sort( + (a, b) => new Date(b.logDateTime) - new Date(a.logDateTime) + ); + + res.status(200).json(mergedLogs); } } else { res.status(403).send(`User (${req.params.userId}) not found.`); diff --git a/src/routes/permissionChangeLogsRouter.js b/src/routes/permissionChangeLogsRouter.js index 50ed7696b..32d3323fd 100644 --- a/src/routes/permissionChangeLogsRouter.js +++ b/src/routes/permissionChangeLogsRouter.js @@ -1,7 +1,7 @@ const express = require('express'); -const routes = function (permissionChangeLog) { - const controller = require('../controllers/permissionChangeLogsController')(permissionChangeLog); +const routes = function (permissionChangeLog, userPermissionChangeLog) { + const controller = require('../controllers/permissionChangeLogsController')(permissionChangeLog, userPermissionChangeLog); const permissionChangeLogRouter = express.Router(); diff --git a/src/routes/userProfileRouter.js b/src/routes/userProfileRouter.js index 794902aa5..3ebb016a1 100644 --- a/src/routes/userProfileRouter.js +++ b/src/routes/userProfileRouter.js @@ -2,7 +2,7 @@ const { body } = require('express-validator'); const express = require('express'); const { ValidationError } = require('../utilities/errorHandling/customError'); -const logUserPermissionController = require('../controllers/logUserPermissionController')(); +const logUserPermissionChangeByAccount = require('../utilities/logUserPermissionChangeByAccount'); const routes = function (userProfile, project) { const controller = require('../controllers/userProfileController')(userProfile, project); @@ -31,6 +31,7 @@ const routes = function (userProfile, project) { .route('/userProfile/:userId') .get(controller.getUserById) .put( + logUserPermissionChangeByAccount, // Middleware for logging permission changes body('firstName').customSanitizer((value) => { if (!value) throw new ValidationError('First Name is required'); return value.trim(); diff --git a/src/startup/routes.js b/src/startup/routes.js index 82a4155a8..c1bc3e476 100644 --- a/src/startup/routes.js +++ b/src/startup/routes.js @@ -25,6 +25,7 @@ const profileInitialSetuptoken = require('../models/profileInitialSetupToken'); const reason = require('../models/reason'); const mouseoverText = require('../models/mouseoverText'); const permissionChangeLog = require('../models/permissionChangeLog'); +const userPermissionChangeLog = require('../models/userPermissionChangeLog'); const mapLocations = require('../models/mapLocation'); const buildingProject = require('../models/bmdashboard/buildingProject'); const buildingNewLesson = require('../models/bmdashboard/buildingNewLesson'); @@ -75,7 +76,7 @@ const profileInitialSetupRouter = require('../routes/profileInitialSetupRouter') mapLocations, ); const permissionChangeLogRouter = require('../routes/permissionChangeLogsRouter')( - permissionChangeLog, + permissionChangeLog, userPermissionChangeLog, ); const isEmailExistsRouter = require('../routes/isEmailExistsRouter')(); diff --git a/src/utilities/logUserPermissionChangeByAccount.js b/src/utilities/logUserPermissionChangeByAccount.js new file mode 100644 index 000000000..57e7edca9 --- /dev/null +++ b/src/utilities/logUserPermissionChangeByAccount.js @@ -0,0 +1,75 @@ +const moment = require('moment-timezone'); +const UserPermissionChangeLog = require('../models/userPermissionChangeLog'); +const UserProfile = require('../models/userProfile'); + +// Middleware function to log permission changes +const logUserPermissionChangeByAccount = async (req, res, next) => { + const { + permissions, firstName, lastName, requestor + } = req.body; + + const dateTime = moment().tz('America/Los_Angeles').format(); + + try { + let permissionsAdded = []; + let permissionsRemoved = []; + const { userId } = req.params; + + // Ensure permissions is always an array + const Permissions = permissions.frontPermissions; + const requestorEmailId = await UserProfile.findById(req.body.requestor.requestorId) + .select('email') + .exec(); + // Find the latest log related to the user's permissions + const document = await findLatestRelatedLog(userId); + + if (document) { + // Ensure document.permissions is an array before filtering + const docPermissions = Array.isArray(document.permissions) ? document.permissions : []; + + // Calculate removed permissions + permissionsRemoved = docPermissions.filter((item) => !Permissions.includes(item)); + // Calculate added permissions + permissionsAdded = Permissions.filter((item) => !docPermissions.includes(item)); + } else { + // If no previous log exists, assume all permissions are new (first time log) + permissionsAdded = Permissions; + } + + const logEntry = new UserPermissionChangeLog({ + logDateTime: dateTime, + userId, + individualName: `${firstName} ${lastName}`, + permissions: Permissions, + permissionsAdded, + permissionsRemoved, + requestorRole: requestor.role, + requestorEmail: requestorEmailId.email, + }); + + // Save the new log entry + await logEntry.save(); + + // Proceed to the next middleware or route + next(); + } catch (error) { + console.error('Error logging permission change:', error); + res.status(500).json({ error: 'Failed to log permission change' }); + } +}; + +// Helper function to find the latest permission log for a user +const findLatestRelatedLog = (userId) => new Promise((resolve, reject) => { + UserPermissionChangeLog.findOne({ userId }) + .sort({ logDateTime: -1 }) + .exec((err, document) => { + if (err) { + console.error(err); + reject(err); + return; + } + resolve(document); + }); +}); + +module.exports = logUserPermissionChangeByAccount; From 91dca93998b293fa715d69be12301a21985a345c Mon Sep 17 00:00:00 2001 From: yashwanth170 Date: Wed, 18 Dec 2024 10:46:45 -0500 Subject: [PATCH 3/4] fix: Dublicate entries are not logged --- src/controllers/userProfileController.js | 2 ++ src/routes/userProfileRouter.js | 2 -- src/utilities/logPermissionChangeByAccount.js | 4 +++ .../logUserPermissionChangeByAccount.js | 31 +++++-------------- 4 files changed, 13 insertions(+), 26 deletions(-) diff --git a/src/controllers/userProfileController.js b/src/controllers/userProfileController.js index 6a23dd34b..d89e26f41 100644 --- a/src/controllers/userProfileController.js +++ b/src/controllers/userProfileController.js @@ -17,6 +17,7 @@ const userService = require('../services/userService'); // const { authorizedUserSara, authorizedUserJae } = process.env; const authorizedUserSara = `nathaliaowner@gmail.com`; // To test this code please include your email here const authorizedUserJae = `jae@onecommunityglobal.org`; +const logUserPermissionChangeByAccount = require('../utilities/logUserPermissionChangeByAccount'); const { hasPermission, canRequestorUpdateUser } = require('../utilities/permissions'); const helper = require('../utilities/permissions'); @@ -696,6 +697,7 @@ const userProfileController = function (UserProfile, Project) { (await hasPermission(req.body.requestor, 'putUserProfilePermissions')) ) { record.permissions = req.body.permissions; + await logUserPermissionChangeByAccount(req); } if (req.body.endDate !== undefined) { diff --git a/src/routes/userProfileRouter.js b/src/routes/userProfileRouter.js index 3ebb016a1..2d68d2da1 100644 --- a/src/routes/userProfileRouter.js +++ b/src/routes/userProfileRouter.js @@ -2,7 +2,6 @@ const { body } = require('express-validator'); const express = require('express'); const { ValidationError } = require('../utilities/errorHandling/customError'); -const logUserPermissionChangeByAccount = require('../utilities/logUserPermissionChangeByAccount'); const routes = function (userProfile, project) { const controller = require('../controllers/userProfileController')(userProfile, project); @@ -31,7 +30,6 @@ const routes = function (userProfile, project) { .route('/userProfile/:userId') .get(controller.getUserById) .put( - logUserPermissionChangeByAccount, // Middleware for logging permission changes body('firstName').customSanitizer((value) => { if (!value) throw new ValidationError('First Name is required'); return value.trim(); diff --git a/src/utilities/logPermissionChangeByAccount.js b/src/utilities/logPermissionChangeByAccount.js index f47c0c5b2..b08e8ee34 100644 --- a/src/utilities/logPermissionChangeByAccount.js +++ b/src/utilities/logPermissionChangeByAccount.js @@ -24,6 +24,10 @@ const changedPermissionsLogger = async (req, res, next) => { permissionsAdded = permissions; } + if (permissionsAdded.length === 0 && permissionsRemoved.length === 0) { + return next(); // No changes, proceed without saving a log + } + const logEntry = new PermissionChangeLog({ logDateTime: dateTime, roleId, diff --git a/src/utilities/logUserPermissionChangeByAccount.js b/src/utilities/logUserPermissionChangeByAccount.js index 57e7edca9..c3e6d7983 100644 --- a/src/utilities/logUserPermissionChangeByAccount.js +++ b/src/utilities/logUserPermissionChangeByAccount.js @@ -2,37 +2,26 @@ const moment = require('moment-timezone'); const UserPermissionChangeLog = require('../models/userPermissionChangeLog'); const UserProfile = require('../models/userProfile'); -// Middleware function to log permission changes -const logUserPermissionChangeByAccount = async (req, res, next) => { - const { - permissions, firstName, lastName, requestor - } = req.body; - +const logUserPermissionChangeByAccount = async (req) => { + const { permissions, firstName, lastName, requestor } = req.body; const dateTime = moment().tz('America/Los_Angeles').format(); try { let permissionsAdded = []; let permissionsRemoved = []; const { userId } = req.params; - - // Ensure permissions is always an array const Permissions = permissions.frontPermissions; - const requestorEmailId = await UserProfile.findById(req.body.requestor.requestorId) - .select('email') - .exec(); - // Find the latest log related to the user's permissions + const requestorEmailId = await UserProfile.findById(requestor.requestorId).select('email').exec(); const document = await findLatestRelatedLog(userId); if (document) { - // Ensure document.permissions is an array before filtering const docPermissions = Array.isArray(document.permissions) ? document.permissions : []; - - // Calculate removed permissions + if(JSON.stringify(docPermissions) === JSON.stringify(Permissions)) { + return; + } permissionsRemoved = docPermissions.filter((item) => !Permissions.includes(item)); - // Calculate added permissions permissionsAdded = Permissions.filter((item) => !docPermissions.includes(item)); } else { - // If no previous log exists, assume all permissions are new (first time log) permissionsAdded = Permissions; } @@ -47,24 +36,18 @@ const logUserPermissionChangeByAccount = async (req, res, next) => { requestorEmail: requestorEmailId.email, }); - // Save the new log entry await logEntry.save(); - - // Proceed to the next middleware or route - next(); + console.log('Permission change logged successfully'); } catch (error) { console.error('Error logging permission change:', error); - res.status(500).json({ error: 'Failed to log permission change' }); } }; -// Helper function to find the latest permission log for a user const findLatestRelatedLog = (userId) => new Promise((resolve, reject) => { UserPermissionChangeLog.findOne({ userId }) .sort({ logDateTime: -1 }) .exec((err, document) => { if (err) { - console.error(err); reject(err); return; } From 5eba92b2c06b9f89ac620429fc0e3c683022d309 Mon Sep 17 00:00:00 2001 From: yashwanth170 Date: Sat, 21 Dec 2024 11:40:47 -0500 Subject: [PATCH 4/4] Fix: Highlighting Individual Name --- src/utilities/logUserPermissionChangeByAccount.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utilities/logUserPermissionChangeByAccount.js b/src/utilities/logUserPermissionChangeByAccount.js index c3e6d7983..700bceaf1 100644 --- a/src/utilities/logUserPermissionChangeByAccount.js +++ b/src/utilities/logUserPermissionChangeByAccount.js @@ -28,7 +28,7 @@ const logUserPermissionChangeByAccount = async (req) => { const logEntry = new UserPermissionChangeLog({ logDateTime: dateTime, userId, - individualName: `${firstName} ${lastName}`, + individualName: `INDIVIDUAL: ${firstName} ${lastName}`, permissions: Permissions, permissionsAdded, permissionsRemoved,