From 7b1eadc381371f2adea79e67a828e2598557152a Mon Sep 17 00:00:00 2001 From: Sathishkumar Krishnan Date: Thu, 23 Dec 2021 18:29:11 +0530 Subject: [PATCH 1/9] feat: enable jwt authentication --- .env.sample | 5 ++- src/config/env.js | 4 ++ src/controller/user.controller.js | 68 ++++++++++++++++++++++++++++++- src/controller/user.router.js | 3 +- src/models/User.js | 13 ++++++ 5 files changed, 89 insertions(+), 4 deletions(-) diff --git a/.env.sample b/.env.sample index a511bee..d9bc16d 100644 --- a/.env.sample +++ b/.env.sample @@ -1,2 +1,5 @@ API_PORT=9000 -MONGODB_URI= \ No newline at end of file +MONGODB_URI= +JWT_SECRET= +JWT_REFRESH_EXPIRY_TIME= +JWT_ACCESS_EXPIRY_TIME= \ No newline at end of file diff --git a/src/config/env.js b/src/config/env.js index 3c7b79a..bc14ef8 100644 --- a/src/config/env.js +++ b/src/config/env.js @@ -3,6 +3,10 @@ require("dotenv").config(); const envVariables = { API_PORT: process.env.API_PORT || "3000", MONGODB_URI: process.env.MONGODB_URI || "mongodb://localhost:12017", + JWT_SECRET: process.env.JWT_SECRET || "secret123", + JWT_REFRESH_EXPIRY_TIME: + parseInt(process.env.JWT_REFRESH_EXPIRY_TIME) || 3600, + JWT_ACCESS_EXPIRY_TIME: parseInt(process.env.JWT_ACCESS_EXPIRY_TIME) || 86400, }; module.exports = envVariables; diff --git a/src/controller/user.controller.js b/src/controller/user.controller.js index 174b483..6242818 100644 --- a/src/controller/user.controller.js +++ b/src/controller/user.controller.js @@ -1,5 +1,69 @@ +const bcrypt = require("bcrypt"); +const jwt = require("jsonwebtoken"); + +const User = require("./../models/User"); +const { + JWT_SECRET, + JWT_REFRESH_EXPIRY_TIME, + JWT_ACCESS_EXPIRY_TIME, +} = require("../../config/env"); + +const createAccessToken = (id) => { + return jwt.sign({ id }, JWT_SECRET, { + expiresIn: JWT_ACCESS_EXPIRY_TIME, + }); +}; + +const createRefreshToken = (id) => { + return jwt.sign({ id }, JWT_SECRET, { + expiresIn: JWT_REFRESH_EXPIRY_TIME, + }); +}; + module.exports = { - getUser: async (req, res) => { - res.send("Not Found"); + registerUser: async (req, res, next) => { + const { email, fullName, password } = req.body; + try { + const salt = await bcrypt.genSalt(); + const newUser = { + email: email, + fullName: fullName, + password: await bcrypt.hash(password, salt), + }; + + const user = await User.create(newUser); + console.log({ msg: "new user created", user }); + + res.send({ success: true, message: "User successfully created!" }); + } catch (err) { + console.log(err); + next(err); + } + }, + + loginUser: async (req, res, next) => { + const { email, password } = req.body; + try { + const user = await User.login(email, password); + + const accessToken = createAccessToken(user._id); + const refreshToken = createRefreshToken(user._id); + + user.refreshToken = refreshToken; + await user.save(); + + res.send({ + success: true, + data: { + email: user.email, + fullName: user.fullName, + accessToken, + refreshToken, + }, + }); + } catch (err) { + console.error(err); + next(err); + } }, }; diff --git a/src/controller/user.router.js b/src/controller/user.router.js index dcd3862..82f5d3f 100644 --- a/src/controller/user.router.js +++ b/src/controller/user.router.js @@ -1,6 +1,7 @@ const router = require("express").Router(); const controller = require("./user.controller"); -router.get("/:id", controller.getUser); +router.post("/register", controller.registerUser); +router.post("/login", controller.loginUser); module.exports = router; diff --git a/src/models/User.js b/src/models/User.js index 9710693..59a8de7 100644 --- a/src/models/User.js +++ b/src/models/User.js @@ -1,6 +1,7 @@ const mongoose = require("mongoose"); const { isEmail } = require("validator"); const { UserActions, WarehouseScopes } = require("./../config/constants"); +const bcrypt = require("bcrypt"); const schema = new mongoose.Schema( { @@ -66,6 +67,18 @@ const schema = new mongoose.Schema( } ); +schema.statics.login = async function (email, password) { + const user = await this.findOne({ email }); + if (user) { + const auth = await bcrypt.compare(password, user.password); + if (auth) { + return user; + } + throw Error("incorrect password"); + } + throw Error("incorrect email"); +}; + const User = mongoose.model("User", schema); module.exports = User; From 75115c050c73b45ae4ca67b43c3ed8c4db67c1fe Mon Sep 17 00:00:00 2001 From: Sathishkumar Krishnan Date: Thu, 23 Dec 2021 18:36:12 +0530 Subject: [PATCH 2/9] feat: added roles and permissions models --- src/models/User.js | 31 +++++++++------------------- src/models/UserPermission.js | 39 ++++++++++++++++++++++++++++++++++++ src/models/UserRole.js | 24 ++++++++++++++++++++++ 3 files changed, 72 insertions(+), 22 deletions(-) create mode 100644 src/models/UserPermission.js create mode 100644 src/models/UserRole.js diff --git a/src/models/User.js b/src/models/User.js index 59a8de7..4e7349c 100644 --- a/src/models/User.js +++ b/src/models/User.js @@ -1,6 +1,5 @@ const mongoose = require("mongoose"); const { isEmail } = require("validator"); -const { UserActions, WarehouseScopes } = require("./../config/constants"); const bcrypt = require("bcrypt"); const schema = new mongoose.Schema( @@ -37,28 +36,16 @@ const schema = new mongoose.Schema( passwordResetToken: { type: String, }, - authPolicies: [ + roles: [ { - inventory: { - type: mongoose.Schema.Types.ObjectId, - ref: "Inventory", - }, - warehouseScope: { - on: { - type: mongoose.Schema.Types.ObjectId, - refPath: "onModel", - }, - onModel: { - type: String, - required: true, - enum: WarehouseScopes, - }, - }, - actions: { - type: String, - required: true, - enum: UserActions, - }, + type: mongoose.Schema.Types.ObjectId, + ref: "UserRole", + }, + ], + permissions: [ + { + type: mongoose.Schema.Types.ObjectId, + ref: "UserPermission", }, ], }, diff --git a/src/models/UserPermission.js b/src/models/UserPermission.js new file mode 100644 index 0000000..30f5fb0 --- /dev/null +++ b/src/models/UserPermission.js @@ -0,0 +1,39 @@ +const mongoose = require("mongoose"); +const { UserActions, WarehouseScopes } = require("./../config/constants"); + +const schema = new mongoose.Schema( + { + name: { + type: String, + required: true, + trim: true, + }, + inventory: { + type: mongoose.Schema.Types.ObjectId, + ref: "Inventory", + }, + warehouseScope: { + on: { + type: mongoose.Schema.Types.ObjectId, + refPath: "onModel", + }, + onModel: { + type: String, + required: true, + enum: WarehouseScopes, + }, + }, + actions: { + type: String, + required: true, + enum: UserActions, + }, + }, + { + timestamps: true, + } +); + +const UserPermission = mongoose.model("UserPermission", schema); + +module.exports = UserPermission; diff --git a/src/models/UserRole.js b/src/models/UserRole.js new file mode 100644 index 0000000..42f528f --- /dev/null +++ b/src/models/UserRole.js @@ -0,0 +1,24 @@ +const mongoose = require("mongoose"); + +const schema = new mongoose.Schema( + { + name: { + type: String, + required: true, + trim: true, + }, + permissions: [ + { + type: mongoose.Schema.Types.ObjectId, + ref: "UserPermission", + }, + ], + }, + { + timestamps: true, + } +); + +const UserRole = mongoose.model("UserRole", schema); + +module.exports = UserRole; From 7727dd56bc6ffb384418321bedac85608fab3ad5 Mon Sep 17 00:00:00 2001 From: Sathishkumar Krishnan Date: Thu, 23 Dec 2021 18:43:55 +0530 Subject: [PATCH 3/9] feat: init roles & permissions routes --- src/controller/user.controller.js | 1 + src/controller/user.router.js | 1 + src/controller/userPermission.controller.js | 7 +++++++ src/controller/userPermission.router.js | 10 ++++++++++ src/controller/userRole.controller.js | 7 +++++++ src/controller/userRole.router.js | 10 ++++++++++ 6 files changed, 36 insertions(+) create mode 100644 src/controller/userPermission.controller.js create mode 100644 src/controller/userPermission.router.js create mode 100644 src/controller/userRole.controller.js create mode 100644 src/controller/userRole.router.js diff --git a/src/controller/user.controller.js b/src/controller/user.controller.js index 6242818..c4f2791 100644 --- a/src/controller/user.controller.js +++ b/src/controller/user.controller.js @@ -66,4 +66,5 @@ module.exports = { next(err); } }, + updateUserAccessControl: async (req, res, next) => {}, }; diff --git a/src/controller/user.router.js b/src/controller/user.router.js index 82f5d3f..c773dc0 100644 --- a/src/controller/user.router.js +++ b/src/controller/user.router.js @@ -3,5 +3,6 @@ const controller = require("./user.controller"); router.post("/register", controller.registerUser); router.post("/login", controller.loginUser); +router.post("/:id/updateAccess", controller.updateUserAccessControl); module.exports = router; diff --git a/src/controller/userPermission.controller.js b/src/controller/userPermission.controller.js new file mode 100644 index 0000000..266acca --- /dev/null +++ b/src/controller/userPermission.controller.js @@ -0,0 +1,7 @@ +module.exports = { + getAllPermissions: async (req, res, next) => {}, + getPermission: async (req, res, next) => {}, + createPermission: async (req, res, next) => {}, + updatePermission: async (req, res, next) => {}, + deletePermission: async (req, res, next) => {}, +}; diff --git a/src/controller/userPermission.router.js b/src/controller/userPermission.router.js new file mode 100644 index 0000000..f450661 --- /dev/null +++ b/src/controller/userPermission.router.js @@ -0,0 +1,10 @@ +const router = require("express").Router(); +const controller = require("./userPermission.controller"); + +router.get("/all", controller.getAllPermissions); +router.get("/:id", controller.getPermission); +router.post("/create", controller.createPermission); +router.patch("/:id", controller.updatePermission); +router.delete("/:id", controller.deletePermission); + +module.exports = router; diff --git a/src/controller/userRole.controller.js b/src/controller/userRole.controller.js new file mode 100644 index 0000000..6321e3f --- /dev/null +++ b/src/controller/userRole.controller.js @@ -0,0 +1,7 @@ +module.exports = { + getAllRoles: async (req, res, next) => {}, + getRole: async (req, res, next) => {}, + createRole: async (req, res, next) => {}, + updateRole: async (req, res, next) => {}, + deleteRole: async (req, res, next) => {}, +}; diff --git a/src/controller/userRole.router.js b/src/controller/userRole.router.js new file mode 100644 index 0000000..b5bc846 --- /dev/null +++ b/src/controller/userRole.router.js @@ -0,0 +1,10 @@ +const router = require("express").Router(); +const controller = require("./userRole.controller"); + +router.get("/all", controller.getAllRoles); +router.get("/:id", controller.getRole); +router.post("/create", controller.createRole); +router.patch("/:id", controller.updateRole); +router.delete("/:id", controller.deleteRole); + +module.exports = router; From 8f4cc159b4344266a19e0690f837dd3f07b951da Mon Sep 17 00:00:00 2001 From: Sathishkumar Krishnan Date: Thu, 23 Dec 2021 22:25:26 +0530 Subject: [PATCH 4/9] feat: crud endpoints for user-permissions --- src/config/constants.js | 3 + src/controller/index.js | 11 ++- src/controller/user.controller.js | 2 +- src/controller/userPermission.controller.js | 101 ++++++++++++++++++-- src/controller/userPermission.router.js | 2 +- src/models/UserPermission.js | 50 ++++++---- 6 files changed, 143 insertions(+), 26 deletions(-) diff --git a/src/config/constants.js b/src/config/constants.js index 0c15e10..13fdf47 100644 --- a/src/config/constants.js +++ b/src/config/constants.js @@ -12,6 +12,8 @@ const UserActions = [ "Receive", ]; +const InventoryScopes = ["Inventory", "Material", "Item"]; + const WarehouseScopes = [ "Warehouse", "Zone", @@ -40,6 +42,7 @@ const CustomAttributeTypes = [ module.exports = { UserActions, + InventoryScopes, WarehouseScopes, InventoryTypes, CustomAttributeTypes, diff --git a/src/controller/index.js b/src/controller/index.js index bdacacf..a63cecb 100644 --- a/src/controller/index.js +++ b/src/controller/index.js @@ -1,10 +1,19 @@ const router = require("express").Router(); const userRouter = require("./user.router"); +const userRoleRouter = require("./userRole.router"); +const userPermissionRouter = require("./userPermission.router"); router.use("/user", userRouter); +router.use("/user-role", userRoleRouter); +router.use("/user-permission", userPermissionRouter); router.get("/", (req, res) => { - res.send("Hello world"); + res.send({ success: true, message: "Hello world" }); +}); + +router.use(function (err, req, res, next) { + console.error(err.stack); + res.status(500).send({ error: `Error: ${err.message}` }); }); module.exports = { router }; diff --git a/src/controller/user.controller.js b/src/controller/user.controller.js index c4f2791..2de0b76 100644 --- a/src/controller/user.controller.js +++ b/src/controller/user.controller.js @@ -6,7 +6,7 @@ const { JWT_SECRET, JWT_REFRESH_EXPIRY_TIME, JWT_ACCESS_EXPIRY_TIME, -} = require("../../config/env"); +} = require("./../config/env"); const createAccessToken = (id) => { return jwt.sign({ id }, JWT_SECRET, { diff --git a/src/controller/userPermission.controller.js b/src/controller/userPermission.controller.js index 266acca..bff698c 100644 --- a/src/controller/userPermission.controller.js +++ b/src/controller/userPermission.controller.js @@ -1,7 +1,96 @@ -module.exports = { - getAllPermissions: async (req, res, next) => {}, - getPermission: async (req, res, next) => {}, - createPermission: async (req, res, next) => {}, - updatePermission: async (req, res, next) => {}, - deletePermission: async (req, res, next) => {}, +const mongoose = require("mongoose"); + +const UserPermission = require("./../models/UserPermission"); +const { InventoryScopes, WarehouseScopes } = require("./../config/constants"); + +const 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.contains(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; +}; + +module.exports = { + getAllPermissions: async (req, res, next) => { + let { page, perPage } = req.query; + page = page || 0; + perPage = perPage || 10; + + const result = await UserPermission.find( + {}, + { id: 1, name: 1, inventoryScopes: 1, warehouseScopes: 1, actions: 1 }, + { skip: page * perPage, limit: perPage } + ); + res.send({ success: true, data: result }); + }, + getPermission: async (req, res, next) => { + try { + const { id } = req.params; + if (mongoose.isValidObjectId(id)) { + const permission = await UserPermission.findById(id); + res.send({ success: true, data: permission }); + } else { + throw new Error(`invalid data format for object-id - ${id}`); + } + } catch (e) { + next(e); + } + }, + createPermission: async (req, res, next) => { + try { + const { name, inventoryScopes, warehouseScopes, actions } = req.body; + const verifiedInventoryScopes = await getScopes( + inventoryScopes, + InventoryScopes + ); + const verifiedWarehouseScopes = await getScopes( + warehouseScopes, + WarehouseScopes + ); + + const newUserPermission = await UserPermission.create({ + name, + inventoryScopes: verifiedInventoryScopes, + warehouseScopes: verifiedWarehouseScopes, + actions: actions == undefined ? [] : actions, + }); + res.send({ success: true, data: newUserPermission }); + } catch (e) { + next(e); + } + }, + updatePermission: async (req, res, next) => { + // Need more clarity + res.send({ success: false, error: "not implemented" }); + }, + deletePermission: async (req, res, next) => { + try { + const { id } = req.params; + if (mongoose.isValidObjectId(id)) { + const result = await UserPermission.deleteOne({ _id: id }); + res.send({ success: true, data: result }); + } else { + throw new Error(`invalid data format for object-id - ${id}`); + } + } catch (e) { + next(e); + } + }, }; diff --git a/src/controller/userPermission.router.js b/src/controller/userPermission.router.js index f450661..ca5d777 100644 --- a/src/controller/userPermission.router.js +++ b/src/controller/userPermission.router.js @@ -4,7 +4,7 @@ const controller = require("./userPermission.controller"); router.get("/all", controller.getAllPermissions); router.get("/:id", controller.getPermission); router.post("/create", controller.createPermission); -router.patch("/:id", controller.updatePermission); +router.post("/:id", controller.updatePermission); router.delete("/:id", controller.deletePermission); module.exports = router; diff --git a/src/models/UserPermission.js b/src/models/UserPermission.js index 30f5fb0..be2cf93 100644 --- a/src/models/UserPermission.js +++ b/src/models/UserPermission.js @@ -1,33 +1,49 @@ const mongoose = require("mongoose"); -const { UserActions, WarehouseScopes } = require("./../config/constants"); +const { + UserActions, + WarehouseScopes, + InventoryScopes, +} = require("./../config/constants"); const schema = new mongoose.Schema( { name: { type: String, required: true, + unique: true, trim: true, }, - inventory: { - type: mongoose.Schema.Types.ObjectId, - ref: "Inventory", - }, - warehouseScope: { - on: { - type: mongoose.Schema.Types.ObjectId, - refPath: "onModel", + inventoryScopes: [ + { + id: { + type: mongoose.Schema.Types.ObjectId, + refPath: "type", + }, + type: { + type: String, + enum: InventoryScopes, + }, }, - onModel: { + ], + warehouseScopes: [ + { + id: { + type: mongoose.Schema.Types.ObjectId, + refPath: "type", + }, + type: { + type: String, + enum: WarehouseScopes, + }, + }, + ], + actions: [ + { type: String, required: true, - enum: WarehouseScopes, + enum: UserActions, }, - }, - actions: { - type: String, - required: true, - enum: UserActions, - }, + ], }, { timestamps: true, From 001566624912ef0582d4cb94b0d24b4db2dcd00b Mon Sep 17 00:00:00 2001 From: Sathishkumar Krishnan Date: Thu, 23 Dec 2021 23:00:16 +0530 Subject: [PATCH 5/9] feat: crud endpoints for user-roles --- src/controller/userRole.controller.js | 80 +++++++++++++++++++++++++-- src/controller/userRole.router.js | 2 +- 2 files changed, 75 insertions(+), 7 deletions(-) diff --git a/src/controller/userRole.controller.js b/src/controller/userRole.controller.js index 6321e3f..0352924 100644 --- a/src/controller/userRole.controller.js +++ b/src/controller/userRole.controller.js @@ -1,7 +1,75 @@ -module.exports = { - getAllRoles: async (req, res, next) => {}, - getRole: async (req, res, next) => {}, - createRole: async (req, res, next) => {}, - updateRole: async (req, res, next) => {}, - deleteRole: async (req, res, next) => {}, +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) + ); + const permissionObjects = await UserPermission.find({ + id: { in: verifiedPermissions }, + }).select({ _id: 1 }); + return permissionObjects.map((_) => _._id); +}; + +module.exports = { + getAllRoles: async (req, res, next) => { + let { page, perPage } = req.query; + page = page || 0; + perPage = perPage || 10; + + const result = await UserRole.find( + {}, + { + id: 1, + name: 1, + permissions: 1, + }, + { skip: page * perPage, limit: perPage } + ); + res.send({ success: true, data: result }); + }, + getRole: async (req, res, next) => { + try { + const { id } = req.params; + if (mongoose.isValidObjectId(id)) { + const role = await UserRole.findById(id); + res.send({ success: true, data: role }); + } else { + throw new Error(`invalid data format for object-id - ${id}`); + } + } catch (e) { + next(e); + } + }, + createRole: async (req, res, next) => { + try { + const { name, permissions } = req.body; + const verifiedPermissions = getValidPermissions(permissions); + const newUserRole = await UserRole.create({ + name, + permissions: verifiedPermissions, + }); + res.send({ success: true, data: newUserRole }); + } catch (e) { + next(e); + } + }, + updateRole: async (req, res, next) => { + // Need more clarity + res.send({ success: false, error: "not implemented" }); + }, + deleteRole: async (req, res, next) => { + try { + const { id } = req.params; + if (mongoose.isValidObjectId(id)) { + const result = await UserRole.deleteOne({ _id: id }); + res.send({ success: true, data: result }); + } else { + throw new Error(`invalid data format for object-id - ${id}`); + } + } catch (e) { + next(e); + } + }, }; diff --git a/src/controller/userRole.router.js b/src/controller/userRole.router.js index b5bc846..0a1f210 100644 --- a/src/controller/userRole.router.js +++ b/src/controller/userRole.router.js @@ -4,7 +4,7 @@ const controller = require("./userRole.controller"); router.get("/all", controller.getAllRoles); router.get("/:id", controller.getRole); router.post("/create", controller.createRole); -router.patch("/:id", controller.updateRole); +router.post("/:id", controller.updateRole); router.delete("/:id", controller.deleteRole); module.exports = router; From e368a9163051c230dd735ff202c7f543f39b9dd1 Mon Sep 17 00:00:00 2001 From: Sathishkumar Krishnan Date: Thu, 23 Dec 2021 23:26:39 +0530 Subject: [PATCH 6/9] feat: added user endpoint to modify access control --- src/controller/user.controller.js | 54 ++++++++++++++++++++++++++- src/controller/user.router.js | 3 +- src/controller/userRole.controller.js | 4 +- 3 files changed, 57 insertions(+), 4 deletions(-) diff --git a/src/controller/user.controller.js b/src/controller/user.controller.js index 2de0b76..04dc2a8 100644 --- a/src/controller/user.controller.js +++ b/src/controller/user.controller.js @@ -1,5 +1,6 @@ const bcrypt = require("bcrypt"); const jwt = require("jsonwebtoken"); +const mongoose = require("mongoose"); const User = require("./../models/User"); const { @@ -7,6 +8,8 @@ const { JWT_REFRESH_EXPIRY_TIME, JWT_ACCESS_EXPIRY_TIME, } = require("./../config/env"); +const UserRole = require("../models/UserRole"); +const UserPermission = require("../models/UserPermission"); const createAccessToken = (id) => { return jwt.sign({ id }, JWT_SECRET, { @@ -20,6 +23,18 @@ const createRefreshToken = (id) => { }); }; +const getValidIds = async (ids, model) => { + const verifiedIds = ids.filter((permission) => + mongoose.isValidObjectId(permission) + ); + const verifiedObjects = await model + .find({ + id: { $in: verifiedIds }, + }) + .select({ _id: 1 }); + return verifiedObjects.map((_) => _._id); +}; + module.exports = { registerUser: async (req, res, next) => { const { email, fullName, password } = req.body; @@ -66,5 +81,42 @@ module.exports = { next(err); } }, - updateUserAccessControl: async (req, res, next) => {}, + + addUserAccessControl: async (req, res, next) => { + const { user, roles, permissions } = 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, { + $push: { + roles: { $each: verifiedRoleIds }, + permissions: { $each: verifiedPermissionIds }, + }, + }); + res.send({ success: true, data: response }); + }, + + removeUserAccessControl: async (req, res, next) => { + const { user, roles, permissions } = 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 }, + }, + }); + res.send({ success: true, data: response }); + }, }; diff --git a/src/controller/user.router.js b/src/controller/user.router.js index c773dc0..02e40e5 100644 --- a/src/controller/user.router.js +++ b/src/controller/user.router.js @@ -3,6 +3,7 @@ const controller = require("./user.controller"); router.post("/register", controller.registerUser); router.post("/login", controller.loginUser); -router.post("/:id/updateAccess", controller.updateUserAccessControl); +router.post("/:id/addAccess", controller.addUserAccessControl); +router.post("/:id/removeAccess", controller.removeUserAccessControl); module.exports = router; diff --git a/src/controller/userRole.controller.js b/src/controller/userRole.controller.js index 0352924..ad5b211 100644 --- a/src/controller/userRole.controller.js +++ b/src/controller/userRole.controller.js @@ -7,7 +7,7 @@ const getValidPermissions = async (permissions) => { mongoose.isValidObjectId(permission) ); const permissionObjects = await UserPermission.find({ - id: { in: verifiedPermissions }, + id: { $in: verifiedPermissions }, }).select({ _id: 1 }); return permissionObjects.map((_) => _._id); }; @@ -45,7 +45,7 @@ module.exports = { createRole: async (req, res, next) => { try { const { name, permissions } = req.body; - const verifiedPermissions = getValidPermissions(permissions); + const verifiedPermissions = await getValidPermissions(permissions); const newUserRole = await UserRole.create({ name, permissions: verifiedPermissions, From 26c4c5411484933bc771d82c2ac6167c3f2c1054 Mon Sep 17 00:00:00 2001 From: Sathishkumar Krishnan Date: Fri, 24 Dec 2021 00:08:06 +0530 Subject: [PATCH 7/9] feat: added authetication middleware --- src/config/authenticator.js | 25 +++++++++++++++++++++++++ src/controller/index.js | 5 +++-- src/controller/user.controller.js | 1 + src/controller/user.router.js | 13 +++++++++++-- 4 files changed, 40 insertions(+), 4 deletions(-) create mode 100644 src/config/authenticator.js diff --git a/src/config/authenticator.js b/src/config/authenticator.js new file mode 100644 index 0000000..e77515a --- /dev/null +++ b/src/config/authenticator.js @@ -0,0 +1,25 @@ +const jwt = require("jsonwebtoken"); +const { JWT_SECRET } = require("./env"); +const User = require("./../models/User"); + +const authenticate = async (token) => { + const decodedToken = jwt.verify(token, JWT_SECRET); + if (decodedToken) { + return await User.findById(decodedToken.id); + } +}; + +module.exports = { + AuthenticateMiddleware: async (req, res, next) => { + try { + const token = req.headers.authorization || ""; + if (token) { + const user = authenticate(token); + res.locals.user = user; + next(); + } + } catch (error) { + res.status(401).send({ success: false, error: "Authentication Failed!" }); + } + }, +}; diff --git a/src/controller/index.js b/src/controller/index.js index a63cecb..89d95b4 100644 --- a/src/controller/index.js +++ b/src/controller/index.js @@ -2,10 +2,11 @@ const router = require("express").Router(); const userRouter = require("./user.router"); const userRoleRouter = require("./userRole.router"); const userPermissionRouter = require("./userPermission.router"); +const { AuthenticateMiddleware } = require("../config/authenticator"); router.use("/user", userRouter); -router.use("/user-role", userRoleRouter); -router.use("/user-permission", userPermissionRouter); +router.use("/user-role", AuthenticateMiddleware, userRoleRouter); +router.use("/user-permission", AuthenticateMiddleware, userPermissionRouter); router.get("/", (req, res) => { res.send({ success: true, message: "Hello world" }); diff --git a/src/controller/user.controller.js b/src/controller/user.controller.js index 04dc2a8..1d704f0 100644 --- a/src/controller/user.controller.js +++ b/src/controller/user.controller.js @@ -64,6 +64,7 @@ module.exports = { const accessToken = createAccessToken(user._id); const refreshToken = createRefreshToken(user._id); + user.accessToken = accessToken; user.refreshToken = refreshToken; await user.save(); diff --git a/src/controller/user.router.js b/src/controller/user.router.js index 02e40e5..420395b 100644 --- a/src/controller/user.router.js +++ b/src/controller/user.router.js @@ -1,9 +1,18 @@ const router = require("express").Router(); const controller = require("./user.controller"); +const { AuthenticateMiddleware } = require("../config/authenticator"); router.post("/register", controller.registerUser); router.post("/login", controller.loginUser); -router.post("/:id/addAccess", controller.addUserAccessControl); -router.post("/:id/removeAccess", controller.removeUserAccessControl); +router.post( + "/:id/addAccess", + AuthenticateMiddleware, + controller.addUserAccessControl +); +router.post( + "/:id/removeAccess", + AuthenticateMiddleware, + controller.removeUserAccessControl +); module.exports = router; From 878ec6d0e0e63f9df1f4c4627fa8332944308d4e Mon Sep 17 00:00:00 2001 From: Sathishkumar Krishnan Date: Fri, 24 Dec 2021 00:58:02 +0530 Subject: [PATCH 8/9] feat: added authorization for one api --- src/config/auth.js | 53 +++++++++++++++++++++++++++++++ src/config/authenticator.js | 25 --------------- src/config/constants.js | 12 +++++++ src/controller/index.js | 2 +- src/controller/user.router.js | 5 ++- src/controller/utils/authorize.js | 16 ++++++++++ 6 files changed, 86 insertions(+), 27 deletions(-) create mode 100644 src/config/auth.js delete mode 100644 src/config/authenticator.js create mode 100644 src/controller/utils/authorize.js diff --git a/src/config/auth.js b/src/config/auth.js new file mode 100644 index 0000000..ec146ed --- /dev/null +++ b/src/config/auth.js @@ -0,0 +1,53 @@ +const jwt = require("jsonwebtoken"); +const { JWT_SECRET } = require("./env"); +const User = require("../models/User"); +const constants = require("./constants"); + +const authenticate = async (token) => { + const decodedToken = jwt.verify(token, JWT_SECRET); + if (decodedToken) { + return await User.findById(decodedToken.id) + .populate({ path: "roles", populate: "permissions" }) + .populate("permissions"); + } +}; + +const authorize = async ( + user, + requiredRoles = [], + requiredPermissions = [] +) => { + 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(_)) + ); +}; + +module.exports = { + AuthenticateMiddleware: async (req, res, next) => { + try { + const token = req.headers.authorization || ""; + if (token) { + const user = authenticate(token); + res.locals.user = user; + next(); + } + } catch (error) { + res + .status(401) + .send({ + success: false, + error: constants.AUTHENTICATION_FAILURE_ERROR_MESSAGE, + }); + } + }, + + AuthorizeUser: authorize, +}; diff --git a/src/config/authenticator.js b/src/config/authenticator.js deleted file mode 100644 index e77515a..0000000 --- a/src/config/authenticator.js +++ /dev/null @@ -1,25 +0,0 @@ -const jwt = require("jsonwebtoken"); -const { JWT_SECRET } = require("./env"); -const User = require("./../models/User"); - -const authenticate = async (token) => { - const decodedToken = jwt.verify(token, JWT_SECRET); - if (decodedToken) { - return await User.findById(decodedToken.id); - } -}; - -module.exports = { - AuthenticateMiddleware: async (req, res, next) => { - try { - const token = req.headers.authorization || ""; - if (token) { - const user = authenticate(token); - res.locals.user = user; - next(); - } - } catch (error) { - res.status(401).send({ success: false, error: "Authentication Failed!" }); - } - }, -}; diff --git a/src/config/constants.js b/src/config/constants.js index 13fdf47..7373070 100644 --- a/src/config/constants.js +++ b/src/config/constants.js @@ -40,10 +40,22 @@ const CustomAttributeTypes = [ "Enumerable", ]; +const AUTHENTICATION_FAILURE_ERROR_MESSAGE = + "Authentication Failed!"; +const AUTHORIZATION_FAILURE_ERROR_MESSAGE = + "User not permitted due to lack of access!"; + module.exports = { UserActions, InventoryScopes, WarehouseScopes, InventoryTypes, CustomAttributeTypes, + SUPER_ADMIN_ROLE: "super-admin", + COMPANY_ADMIN_ROLE: "company-admin", + WAREHOUSE_ADMIN_ROLE: "warehouse-admin", + ZONE_ADMIN_ROLE: "zone-admin", + AREA_ADMIN_ROLE: "area-admin", + AUTHENTICATION_FAILURE_ERROR_MESSAGE, + AUTHORIZATION_FAILURE_ERROR_MESSAGE, }; diff --git a/src/controller/index.js b/src/controller/index.js index 89d95b4..6d2d094 100644 --- a/src/controller/index.js +++ b/src/controller/index.js @@ -2,7 +2,7 @@ const router = require("express").Router(); const userRouter = require("./user.router"); const userRoleRouter = require("./userRole.router"); const userPermissionRouter = require("./userPermission.router"); -const { AuthenticateMiddleware } = require("../config/authenticator"); +const { AuthenticateMiddleware } = require("../config/auth"); router.use("/user", userRouter); router.use("/user-role", AuthenticateMiddleware, userRoleRouter); diff --git a/src/controller/user.router.js b/src/controller/user.router.js index 420395b..7c0c39d 100644 --- a/src/controller/user.router.js +++ b/src/controller/user.router.js @@ -1,17 +1,20 @@ const router = require("express").Router(); const controller = require("./user.controller"); -const { AuthenticateMiddleware } = require("../config/authenticator"); +const { AuthenticateMiddleware } = require("../config/auth"); +const { SuperAdminCheck } = require("./utils/authorize"); router.post("/register", controller.registerUser); router.post("/login", controller.loginUser); router.post( "/:id/addAccess", AuthenticateMiddleware, + SuperAdminCheck, controller.addUserAccessControl ); router.post( "/:id/removeAccess", AuthenticateMiddleware, + SuperAdminCheck, controller.removeUserAccessControl ); diff --git a/src/controller/utils/authorize.js b/src/controller/utils/authorize.js new file mode 100644 index 0000000..4c17cdc --- /dev/null +++ b/src/controller/utils/authorize.js @@ -0,0 +1,16 @@ +const UserRole = require("../../models/UserRole"); +const { AuthorizeUser } = require("../../config/auth"); +const { SUPER_ADMIN_ROLE, AUTHORIZATION_FAILURE_ERROR_MESSAGE } = require("../../config/constants"); + +module.exports = { + SuperAdminCheck: async (req, res, next) => { + const SuperAdmin = await UserRole.findOne({ name: SUPER_ADMIN_ROLE }); + if (AuthorizeUser(req.locals.user, [SuperAdmin.id])) { + next(); + } else { + res + .status(403) + .send({ success: false, error: AUTHORIZATION_FAILURE_ERROR_MESSAGE }); + } + }, +}; From 9cb5d898f49ae327dc2c7df90fba99f037f90128 Mon Sep 17 00:00:00 2001 From: Sathishkumar Krishnan Date: Mon, 27 Dec 2021 12:23:41 +0530 Subject: [PATCH 9/9] fix: added success flag to err handler --- src/controller/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/controller/index.js b/src/controller/index.js index 138e5fd..64bc9dd 100644 --- a/src/controller/index.js +++ b/src/controller/index.js @@ -30,7 +30,7 @@ router.get("/", (req, res) => { router.use(function (err, req, res, next) { console.error(err.stack); - res.status(500).send({ error: `Error: ${err.message}` }); + res.status(500).send({ success: false, error: `Error: ${err.message}` }); }); module.exports = { router };