UNPKG

@mamoorali295/rbac

Version:

Complete RBAC (Role-Based Access Control) system for Node.js with Express middleware, NestJS integration, GraphQL support, MongoDB & PostgreSQL support, modern admin dashboard, TypeScript support, and dynamic permission management

461 lines (460 loc) 19 kB
"use strict"; var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; Object.defineProperty(exports, "__esModule", { value: true }); exports.userRoleController = void 0; const UserRole_1 = require("../models/UserRole"); const User_1 = require("../models/User"); const mongoose_1 = require("mongoose"); const Permission_1 = require("../models/Permission"); /** * Retrieves all user roles from the database. * Roles define collections of features with specific permissions that can be assigned to users. * * @returns {Promise<{message: string, userRoles: any[]} | {error: string}>} * Success response with roles array or error response * * @example * ```typescript * const { userRoleController } = RBAC.controllers; * const result = await userRoleController.getAllRoles(); * * if (result.error) { * console.error('Failed to fetch roles:', result.error); * } else { * console.log('Available roles:', result.userRoles); * // result.userRoles = [{ _id: '...', name: 'admin', description: '...', features: [...] }, ...] * } * ``` */ const getAllRoles = () => __awaiter(void 0, void 0, void 0, function* () { try { const userRoles = yield UserRole_1.UserRole.find().exec(); return { message: "User Roles fetched successfully", userRoles }; } catch (error) { return { error: "Internal server error" }; } }); /** * Creates a new role in the RBAC system. * Roles define collections of features with specific permissions that can be assigned to users. * * @param {string} name - Unique name for the role (e.g., 'admin', 'manager', 'editor') * @param {string} description - Human-readable description of the role * @param {IFeaturePermission[]} features - Array of feature-permission mappings * @param {string} features[].feature - MongoDB ObjectId of the feature * @param {string[]} features[].permissions - Array of MongoDB ObjectIds of permissions * @returns {Promise<{message: string, userRole: any} | {error: string}>} * Success response with created role or error response * * @example * ```typescript * const { userRoleController, featureController } = RBAC.controllers; * * // Get feature and permission IDs first * const { features } = await featureController.getAllFeatures(); * const { permissions } = await userRoleController.getPermissions(); * * const billingFeature = features.find(f => f.name === 'billing'); * const readPerm = permissions.find(p => p.name === 'read'); * const createPerm = permissions.find(p => p.name === 'create'); * * const roleFeatures = [{ * feature: billingFeature._id, * permissions: [readPerm._id, createPerm._id] * }]; * * const result = await userRoleController.createRole( * 'billing-manager', * 'Manager for billing operations', * roleFeatures * ); * * if (result.error) { * console.error('Failed to create role:', result.error); * } else { * console.log('Role created:', result.userRole); * } * ``` */ const createRole = (name, description, features) => __awaiter(void 0, void 0, void 0, function* () { try { const existingRole = yield UserRole_1.UserRole.findOne({ name }).exec(); if (existingRole) { return { error: "Role with this name already exists" }; } const newRole = new UserRole_1.UserRole({ name, description, features: features, }); const savedRole = yield newRole.save(); return { message: "User Role created successfully", userRole: savedRole }; } catch (error) { return { error: "Internal server error" }; } }); /** * Deletes a role from the RBAC system. * Prevents deletion if the role is currently assigned to any users. * * @param {string} roleId - MongoDB ObjectId of the role to delete * @returns {Promise<{message: string} | {error: string}>} * Success message or error response * * @example * ```typescript * const { userRoleController } = RBAC.controllers; * * const result = await userRoleController.deleteRole('507f1f77bcf86cd799439011'); * * if (result.error) { * console.error('Failed to delete role:', result.error); * // Could be: "User role can not be deleted as it is assigned to one or more users" * } else { * console.log(result.message); // "User Role deleted successfully" * } * ``` * * @warning This function checks for users assigned to the role before deletion. * Roles assigned to users cannot be deleted. */ const deleteRole = (roleId) => __awaiter(void 0, void 0, void 0, function* () { try { const usersWithRole = yield User_1.User.find({ role: new mongoose_1.Types.ObjectId(roleId) }).exec(); if (usersWithRole.length > 0) return { error: "User role can not be deleted as it is assigned to one or more users" }; const deletedRole = yield UserRole_1.UserRole.findByIdAndDelete(roleId).exec(); return { message: "User Role deleted successfully" }; } catch (error) { return { error: "Internal server error" }; } }); /** * Adds features to an existing role. * Features are added with empty permissions - use addPermissionToFeatureInUserRole to add permissions. * * @param {string} roleId - MongoDB ObjectId of the role to modify * @param {string[]} featureIds - Array of MongoDB ObjectIds of features to add * @returns {Promise<{message: string, userRole: any} | {error: string}>} * Success response with updated role or error response * * @example * ```typescript * const { userRoleController, featureController } = RBAC.controllers; * * // Get available features * const { features } = await featureController.getAllFeatures(); * const billingFeature = features.find(f => f.name === 'billing'); * const reportsFeature = features.find(f => f.name === 'reports'); * * const result = await userRoleController.addFeatureToUserRole( * '507f1f77bcf86cd799439011', // roleId * [billingFeature._id, reportsFeature._id] * ); * * if (result.error) { * console.error('Failed to add features:', result.error); * } else { * console.log('Features added successfully:', result.userRole); * } * ``` * * @note Features are added with empty permissions. Use addPermissionToFeatureInUserRole * to assign specific permissions to the newly added features. */ const addFeatureToUserRole = (roleId, featureIds) => __awaiter(void 0, void 0, void 0, function* () { var _a; try { const role = yield UserRole_1.UserRole.findById(roleId).exec(); if (!role) { return { error: "Role not found" }; } const existingFeatureIds = ((_a = role.features) === null || _a === void 0 ? void 0 : _a.map((f) => { var _a; return (_a = f.feature) === null || _a === void 0 ? void 0 : _a.toString(); })) || []; const newFeatures = featureIds .filter((featureId) => !existingFeatureIds.includes(featureId)) .map((featureId) => ({ feature: new mongoose_1.Types.ObjectId(featureId), permissions: [], })); if (newFeatures.length === 0) { return { error: "All features already exist in this role" }; } role.features = [...(role.features || []), ...newFeatures]; yield role.save(); return { message: "Features added to user role successfully", userRole: role }; } catch (error) { return { error: "Internal server error" }; } }); /** * Removes features from an existing role. * Removes the features and all their associated permissions from the role. * * @param {string} roleId - MongoDB ObjectId of the role to modify * @param {string[]} featureIds - Array of MongoDB ObjectIds of features to remove * @returns {Promise<{message: string, userRole: any} | {error: string}>} * Success response with updated role or error response * * @example * ```typescript * const { userRoleController, featureController } = RBAC.controllers; * * // Get feature to remove * const { features } = await featureController.getAllFeatures(); * const billingFeature = features.find(f => f.name === 'billing'); * * const result = await userRoleController.removeFeatureFromUserRole( * '507f1f77bcf86cd799439011', // roleId * [billingFeature._id] * ); * * if (result.error) { * console.error('Failed to remove features:', result.error); * } else { * console.log('Features removed successfully:', result.userRole); * } * ``` * * @warning This completely removes the feature and all its permissions from the role. * Users with this role will lose access to the removed features. */ const removeFeatureFromUserRole = (roleId, featureIds) => __awaiter(void 0, void 0, void 0, function* () { var _a, _b; try { const role = yield UserRole_1.UserRole.findById(roleId).exec(); if (!role) { return { error: "Role not found" }; } const initialFeatureCount = ((_a = role.features) === null || _a === void 0 ? void 0 : _a.length) || 0; role.features = ((_b = role.features) === null || _b === void 0 ? void 0 : _b.filter((f) => { var _a; return !featureIds.includes(((_a = f.feature) === null || _a === void 0 ? void 0 : _a.toString()) || ""); })) || []; if (role.features.length === initialFeatureCount) { return { error: "No matching features found to remove" }; } yield role.save(); return { message: "Features removed from user role successfully", userRole: role }; } catch (error) { return { error: "Internal server error" }; } }); /** * Adds permissions to specific features within a role. * Allows granular control over what actions a role can perform on each feature. * * @param {string} roleId - MongoDB ObjectId of the role to modify * @param {string[]} featureIds - Array of MongoDB ObjectIds of features to modify * @param {string[]} permissionIds - Array of MongoDB ObjectIds of permissions to add * @returns {Promise<{message: string, userRole: any} | {error: string}>} * Success response with updated role or error response * * @example * ```typescript * const { userRoleController, featureController } = RBAC.controllers; * * // Get features and permissions * const { features } = await featureController.getAllFeatures(); * const { permissions } = await userRoleController.getPermissions(); * * const billingFeature = features.find(f => f.name === 'billing'); * const readPerm = permissions.find(p => p.name === 'read'); * const createPerm = permissions.find(p => p.name === 'create'); * * const result = await userRoleController.addPermissionToFeatureInUserRole( * '507f1f77bcf86cd799439011', // roleId * [billingFeature._id], // features to modify * [readPerm._id, createPerm._id] // permissions to add * ); * * if (result.error) { * console.error('Failed to add permissions:', result.error); * } else { * console.log('Permissions added successfully:', result.userRole); * } * ``` * * @note Only adds permissions that don't already exist on the features. * Duplicate permissions are automatically filtered out. */ const addPermissionToFeatureInUserRole = (roleId, featureIds, permissionIds) => __awaiter(void 0, void 0, void 0, function* () { try { const role = yield UserRole_1.UserRole.findById(roleId).exec(); if (!role) { return { error: "Role not found" }; } if (!role.features || role.features.length === 0) { return { error: "No features found in this role" }; } let updated = false; role.features.forEach((feature) => { var _a; if (featureIds.includes(((_a = feature.feature) === null || _a === void 0 ? void 0 : _a.toString()) || "")) { const existingPermissionIds = (feature.permissions || []).map((p) => p.toString()); const newPermissions = permissionIds.filter((permId) => !existingPermissionIds.includes(permId)).map((permId) => new mongoose_1.Types.ObjectId(permId)); if (newPermissions.length > 0) { // @ts-ignore feature.permissions = [...((feature === null || feature === void 0 ? void 0 : feature.permissions) || []), ...newPermissions]; updated = true; } } }); if (!updated) { return { error: "No new permissions to add or features not found" }; } yield role.save(); return { message: "Permissions added successfully", userRole: role }; } catch (error) { return { error: "Internal server error" }; } }); /** * Removes permissions from specific features within a role. * Allows fine-grained control by removing specific permissions while keeping the feature. * * @param {string} roleId - MongoDB ObjectId of the role to modify * @param {string[]} featureIds - Array of MongoDB ObjectIds of features to modify * @param {string[]} permissionIds - Array of MongoDB ObjectIds of permissions to remove * @returns {Promise<{message: string, userRole: any} | {error: string}>} * Success response with updated role or error response * * @example * ```typescript * const { userRoleController, featureController } = RBAC.controllers; * * // Get features and permissions * const { features } = await featureController.getAllFeatures(); * const { permissions } = await userRoleController.getPermissions(); * * const billingFeature = features.find(f => f.name === 'billing'); * const deletePerm = permissions.find(p => p.name === 'delete'); * const sudoPerm = permissions.find(p => p.name === 'sudo'); * * const result = await userRoleController.removePermissionToFeatureInUserRole( * '507f1f77bcf86cd799439011', // roleId * [billingFeature._id], // features to modify * [deletePerm._id, sudoPerm._id] // permissions to remove * ); * * if (result.error) { * console.error('Failed to remove permissions:', result.error); * } else { * console.log('Permissions removed successfully:', result.userRole); * } * ``` * * @note This removes specific permissions while keeping the feature in the role. * To remove the entire feature, use removeFeatureFromUserRole instead. */ const removePermissionToFeatureInUserRole = (roleId, featureIds, permissionIds) => __awaiter(void 0, void 0, void 0, function* () { try { const role = yield UserRole_1.UserRole.findById(roleId).exec(); if (!role) { return { error: "Role not found" }; } if (!role.features || role.features.length === 0) { return { error: "No features found in this role" }; } let updated = false; role.features.forEach((feature) => { var _a, _b, _c; if (featureIds.includes(((_a = feature.feature) === null || _a === void 0 ? void 0 : _a.toString()) || "")) { const initialPermissionCount = ((_b = feature.permissions) === null || _b === void 0 ? void 0 : _b.length) || 0; // @ts-ignore feature.permissions = ((feature === null || feature === void 0 ? void 0 : feature.permissions) || []).filter((permission) => !permissionIds.includes(permission.toString())); // @ts-ignore if (((_c = feature === null || feature === void 0 ? void 0 : feature.permissions) === null || _c === void 0 ? void 0 : _c.length) < initialPermissionCount) { updated = true; } } }); if (!updated) { return { error: "No matching permissions found to remove" }; } yield role.save(); return { message: "Permissions removed successfully", userRole: role }; } catch (error) { return { error: "Internal server error" }; } }); /** * Retrieves all available permissions from the database. * Permissions define the granular actions that can be performed (read, create, update, delete, sudo). * * @returns {Promise<{message: string, permissions: any[]} | {error: string}>} * Success response with permissions array or error response * * @example * ```typescript * const { userRoleController } = RBAC.controllers; * const result = await userRoleController.getPermissions(); * * if (result.error) { * console.error('Failed to fetch permissions:', result.error); * } else { * console.log('Available permissions:', result.permissions); * // result.permissions = [ * // { _id: '...', name: 'read', description: 'View and access resources' }, * // { _id: '...', name: 'create', description: 'Add new resources' }, * // { _id: '...', name: 'update', description: 'Modify existing resources' }, * // { _id: '...', name: 'delete', description: 'Remove resources' }, * // { _id: '...', name: 'sudo', description: 'Full administrative access' } * // ] * } * ``` * * @note Standard permissions (read, create, update, delete, sudo) are auto-created during RBAC initialization. */ const getPermissions = () => __awaiter(void 0, void 0, void 0, function* () { try { const permissions = yield Permission_1.Permission.find().exec(); return { message: "Permissions fetched successfully", permissions }; } catch (error) { return { error: "Internal server error" }; } }); /** * User role controller providing comprehensive CRUD operations for RBAC roles and permissions. * Manages the complex relationships between roles, features, and permissions. * * @namespace userRoleController * * @example * ```typescript * import { RBAC } from '@sheikh295/rbac'; * const { userRoleController } = RBAC.controllers; * * // Get all roles * const { userRoles } = await userRoleController.getAllRoles(); * * // Get all permissions * const { permissions } = await userRoleController.getPermissions(); * * // Create a new role with features and permissions * const roleFeatures = [{ feature: featureId, permissions: [permissionId1, permissionId2] }]; * await userRoleController.createRole('manager', 'Department manager', roleFeatures); * ``` */ exports.userRoleController = { getAllRoles, createRole, deleteRole, addFeatureToUserRole, removeFeatureFromUserRole, addPermissionToFeatureInUserRole, removePermissionToFeatureInUserRole, getPermissions, };