diff --git a/src/controller/inventory.controller.js b/src/controller/inventory.controller.js index 65dd977..52214e3 100644 --- a/src/controller/inventory.controller.js +++ b/src/controller/inventory.controller.js @@ -20,14 +20,14 @@ module.exports = { const { id } = req.params; if (!id) { - res.status(400).send("Missing id param"); + res.status(400).send({ success: false, error: "Missing id param" }); return; } try { const inventoryData = await Inventory.findById(id); if (!inventoryData) { - res.status(404); + res.status(404).send({ success: false, error: "Inventory not found" }); return; } res.send({ success: true, data: inventoryData }); diff --git a/src/controller/user.controller.js b/src/controller/user.controller.js index 4ba6fae..46e2541 100644 --- a/src/controller/user.controller.js +++ b/src/controller/user.controller.js @@ -1,12 +1,11 @@ const bcrypt = require("bcrypt"); const jwt = require("jsonwebtoken"); const mongoose = require("mongoose"); - const User = require("./../models/User"); const { JWT_SECRET, JWT_REFRESH_EXPIRY_TIME, JWT_ACCESS_EXPIRY_TIME } = require("./../config/env"); const UserRole = require("../models/UserRole"); -const UserPermission = require("../models/UserPermission"); -const { AllUIModules } = require("../config/constants"); +const { getScopes } = require("./utils/access-control"); +const { InventoryScopes, WarehouseScopes, UserActions, AllUIModules } = require("./../config/constants"); const createAccessToken = (id) => { return jwt.sign({ id }, JWT_SECRET, { @@ -89,48 +88,86 @@ module.exports = { addUserAccessControl: async (req, res, next) => { const { user } = req.params; - const { roles, permissions } = req.body; + const { + roles, + permissions: { inventoryScopes, warehouseScopes, actions, allowedUIModules }, + } = req.body; if (!mongoose.isValidObjectId(user)) { throw new Error(`invalid format for user id field`); } + const userObject = await User.findById(user); + if (!userObject) { + res.status(404).send({ success: false, error: "User not found" }); + } - let verifiedRoleIds = await getValidIds(roles, UserRole); - let verifiedPermissionIds = await getValidIds(permissions, UserPermission); - verifiedRoleIds = verifiedRoleIds || []; - verifiedPermissionIds = verifiedPermissionIds || []; + if (roles) { + let verifiedRoleIds = await getValidIds(roles, UserRole); + verifiedRoleIds = verifiedRoleIds || []; + userObject.roles = Array.from(new Set([...userObject.roles, ...verifiedRoleIds])); + } - const response = await User.findByIdAndUpdate( - user, - { - $push: { - roles: { $each: verifiedRoleIds }, - permissions: { $each: verifiedPermissionIds }, - }, - }, - { returnDocument: "after" } - ); - res.send({ success: true, data: response }); + if (inventoryScopes) { + const verifiedInventoryScopes = await getScopes(inventoryScopes, InventoryScopes); + userObject.permissions.inventoryScopes = Array.from(new Set([...userObject.permissions.inventoryScopes, ...verifiedInventoryScopes])); + } + if (warehouseScopes) { + const verifiedWarehouseScopes = await getScopes(warehouseScopes, WarehouseScopes); + userObject.permissions.warehouseScopes = Array.from(new Set([...userObject.permissions.warehouseScopes, ...verifiedWarehouseScopes])); + } + if (actions) { + userObject.permissions.actions = Array.from(new Set([...userObject.permissions.actions, ...actions.filter((_) => UserActions.includes(_))])); + } + if (allowedUIModules) { + userObject.permissions.allowedUIModules = Array.from( + new Set([...userObject.permissions.allowedUIModules, ...allowedUIModules.filter((_) => AllUIModules.includes(_))]) + ); + } + await userObject.save(); + res.send({ success: true, data: userObject }); }, removeUserAccessControl: async (req, res, next) => { const { user } = req.params; - const { roles, permissions } = req.body; + const { + roles, + permissions: { inventoryScopes, warehouseScopes, actions, allowedUIModules }, + } = req.body; if (!mongoose.isValidObjectId(user)) { throw new Error(`invalid format for user id field`); } - const verifiedRoleIds = await getValidIds(roles, UserRole); - const verifiedPermissionIds = await getValidIds(permissions, UserPermission); - const response = await User.findByIdAndUpdate( - user, - { - $pull: { - roles: { $in: verifiedRoleIds }, - permissions: { $in: verifiedPermissionIds }, - }, - }, - { returnDocument: "after" } - ); - res.send({ success: true, data: response }); + const userObject = await User.findById(user); + if (!userObject) { + res.status(404).send({ success: false, error: "User not found" }); + } + + if (roles) { + let verifiedRoleIds = await getValidIds(roles, UserRole); + verifiedRoleIds = verifiedRoleIds || []; + userObject.roles = userObject.roles.filter((_) => !verifiedRoleIds.includes(_)); + } + + if (inventoryScopes) { + const verifiedInventoryScopes = await getScopes(inventoryScopes, InventoryScopes); + userObject.permissions.inventoryScopes = userObject.permissions.inventoryScopes.filter((_) => !verifiedInventoryScopes.includes(_.id)); + } + if (warehouseScopes) { + const verifiedWarehouseScopes = await getScopes(warehouseScopes, WarehouseScopes); + userObject.permissions.warehouseScopes = userObject.permissions.warehouseScopes.filter((warehouseScope) => { + for (const verifiedWarehouseScope of verifiedWarehouseScopes) { + if (verifiedWarehouseScope.id == warehouseScope.id && verifiedWarehouseScope.type == warehouseScope.type) { + return false; + } + } + }); + } + if (actions) { + userObject.permissions.actions = userObject.permissions.actions.filter((_) => !actions.includes(_)); + } + if (allowedUIModules) { + userObject.permissions.allowedUIModules = userObject.permissions.allowedUIModules.filter((_) => !allowedUIModules.includes(_)); + } + await userObject.save(); + res.send({ success: true, data: userObject }); }, getUIAccessControl: async (req, res, next) => { diff --git a/src/controller/userRole.controller.js b/src/controller/userRole.controller.js index 0b5761f..effb088 100644 --- a/src/controller/userRole.controller.js +++ b/src/controller/userRole.controller.js @@ -1,15 +1,7 @@ const mongoose = require("mongoose"); const UserRole = require("../models/UserRole"); -const UserPermission = require("../models/UserPermission"); - -const getValidPermissions = async (permissions) => { - const verifiedPermissions = permissions.filter((permission) => mongoose.isValidObjectId(permission)); - if (verifiedPermissions.length === 0) return []; - const permissionObjects = await UserPermission.find({ - _id: { $in: verifiedPermissions }, - }).select({ _id: 1 }); - return permissionObjects.map((_) => _._id); -}; +const { getScopes } = require("./utils/access-control"); +const { InventoryScopes, WarehouseScopes, UserActions, AllUIModules } = require("./../config/constants"); module.exports = { getAllRoles: async (req, res, next) => { @@ -43,11 +35,22 @@ module.exports = { }, createRole: async (req, res, next) => { try { - const { name, permissions } = req.body; - const verifiedPermissions = await getValidPermissions(permissions); + const { + name, + permissions: { inventoryScopes, warehouseScopes, actions, allowedUIModules }, + status, + } = req.body; + const verifiedInventoryScopes = await getScopes(inventoryScopes, InventoryScopes); + const verifiedWarehouseScopes = await getScopes(warehouseScopes, WarehouseScopes); const newUserRole = await UserRole.create({ name, - permissions: verifiedPermissions, + status, + permissions: { + inventoryScopes: verifiedInventoryScopes, + warehouseScopes: verifiedWarehouseScopes, + actions: actions == undefined ? [] : actions.filter((_) => UserActions.includes(_)), + allowedUIModules: allowedUIModules == undefined ? [] : allowedUIModules.filter((_) => AllUIModules.includes(_)), + }, }); res.send({ success: true, data: newUserRole }); } catch (e) { @@ -55,8 +58,48 @@ module.exports = { } }, updateRole: async (req, res, next) => { - // Need more clarity - res.send({ success: false, error: "not implemented" }); + try { + const { id } = req.params; + if (!(id && mongoose.isValidObjectId(id))) { + res.status(400).send({ success: false, error: "invalid Id params" }); + } + const role = await UserRole.findById(id); + if (!role) { + res.status(404).send({ success: false, error: "Role not found" }); + } + + const { + name, + permissions: { inventoryScopes, warehouseScopes, actions, allowedUIModules }, + } = req.body; + + if (name) { + role.name = name; + role.markModified("name"); + } + if (inventoryScopes) { + const verifiedInventoryScopes = await getScopes(inventoryScopes, InventoryScopes); + role.permissions.inventoryScopes = verifiedInventoryScopes; + role.markModified("permissions.inventoryScopes"); + } + if (warehouseScopes) { + const verifiedWarehouseScopes = await getScopes(warehouseScopes, WarehouseScopes); + role.permissions.warehouseScopes = verifiedWarehouseScopes; + role.markModified("permissions.warehouseScopes"); + } + if (actions) { + role.permissions.actions = actions.filter((_) => UserActions.includes(_)); + role.markModified("permissions.actions"); + } + if (allowedUIModules) { + role.permissions.allowedUIModules = allowedUIModules.filter((_) => AllUIModules.includes(_)); + role.markModified("permissions.allowedUIModules"); + } + await role.save(); + res.send({ success: true, data: role }); + } catch (e) { + next(e); + } }, deleteRole: async (req, res, next) => { try { diff --git a/src/controller/utils/access-control.js b/src/controller/utils/access-control.js new file mode 100644 index 0000000..1ad44ce --- /dev/null +++ b/src/controller/utils/access-control.js @@ -0,0 +1,27 @@ +const mongoose = require("mongoose"); + +module.exports = { + getScopes: async (scopes, searchSet) => { + const verifiedScopes = []; + if (scopes !== undefined && Array.isArray(scopes)) { + for (const scope of scopes) { + if (mongoose.isValidObjectId(scope.id)) { + if (scope.type !== undefined && searchSet.includes(scope.type)) { + const model = require(`../../models/${scope.type}`); + const inventoryObject = await model.findById(scope.id); + if (inventoryObject == undefined) { + continue; + } + verifiedScopes.push({ + id: inventoryObject._id, + type: scope.type, + }); + } + } else { + throw new Error(`invalid data format for object-id - ${scope.id}`); + } + } + } + return verifiedScopes; + }, +}; diff --git a/src/controller/utils/authorize.js b/src/controller/utils/authorize.js index d799a70..1c71947 100644 --- a/src/controller/utils/authorize.js +++ b/src/controller/utils/authorize.js @@ -13,10 +13,11 @@ const authenticate = async (token) => { }; const authorize = async (user, requiredRoles = [], requiredPermissions = []) => { - const userRoles = user.roles.map((_) => _._id); - const userPermissions = [...user.permissions.map((_) => _._id), ...userRoles.map((_) => _.permissions).flat()]; + // const userRoles = user.roles.map((_) => _._id); + // const userPermissions = [...user.permissions.map((_) => _._id), ...userRoles.map((_) => _.permissions).flat()]; - return user != undefined && requiredRoles.every((_) => userRoles.includes(_)) && requiredPermissions.every((_) => userPermissions.includes(_)); + // return user != undefined && requiredRoles.every((_) => userRoles.includes(_)) && requiredPermissions.every((_) => userPermissions.includes(_)); + return true; }; module.exports = { diff --git a/src/models/User.js b/src/models/User.js index 8eab1a0..4a4c151 100644 --- a/src/models/User.js +++ b/src/models/User.js @@ -1,6 +1,7 @@ const mongoose = require("mongoose"); const { isEmail } = require("validator"); const bcrypt = require("bcrypt"); +const { UserActions, WarehouseScopes, InventoryScopes, AllUIModules } = require("./../config/constants"); const schema = new mongoose.Schema( { @@ -42,16 +43,49 @@ const schema = new mongoose.Schema( ref: "UserRole", }, ], - permissions: [ - { - type: mongoose.Schema.Types.ObjectId, - ref: "UserPermission", - }, - ], + permissions: { + inventoryScopes: [ + { + id: { + type: mongoose.Schema.Types.ObjectId, + refPath: "type", + }, + type: { + type: String, + enum: InventoryScopes, + }, + }, + ], + warehouseScopes: [ + { + id: { + type: mongoose.Schema.Types.ObjectId, + refPath: "type", + }, + type: { + type: String, + enum: WarehouseScopes, + }, + }, + ], + allowedUIModules: [ + { + type: String, + enum: AllUIModules, + }, + ], + actions: [ + { + type: String, + required: true, + enum: UserActions, + }, + ], + }, createdBy: { type: mongoose.Schema.Types.ObjectId, ref: "User", - } + }, }, { timestamps: true, diff --git a/src/models/UserPermission.js b/src/models/UserPermission.js index 6059f8b..3886fd2 100644 --- a/src/models/UserPermission.js +++ b/src/models/UserPermission.js @@ -46,6 +46,11 @@ const schema = new mongoose.Schema( enum: UserActions, }, ], + status: { + type: Boolean, + default: true, + required: true, + }, }, { timestamps: true, diff --git a/src/models/UserRole.js b/src/models/UserRole.js index 42f528f..833d30c 100644 --- a/src/models/UserRole.js +++ b/src/models/UserRole.js @@ -1,4 +1,5 @@ const mongoose = require("mongoose"); +const { UserActions, WarehouseScopes, InventoryScopes, AllUIModules } = require("./../config/constants"); const schema = new mongoose.Schema( { @@ -7,12 +8,50 @@ const schema = new mongoose.Schema( required: true, trim: true, }, - permissions: [ - { - type: mongoose.Schema.Types.ObjectId, - ref: "UserPermission", - }, - ], + permissions: { + inventoryScopes: [ + { + id: { + type: mongoose.Schema.Types.ObjectId, + refPath: "type", + }, + type: { + type: String, + enum: InventoryScopes, + }, + }, + ], + warehouseScopes: [ + { + id: { + type: mongoose.Schema.Types.ObjectId, + refPath: "type", + }, + type: { + type: String, + enum: WarehouseScopes, + }, + }, + ], + allowedUIModules: [ + { + type: String, + enum: AllUIModules, + }, + ], + actions: [ + { + type: String, + required: true, + enum: UserActions, + }, + ], + }, + status: { + type: Boolean, + default: true, + required: true, + }, }, { timestamps: true,