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

425 lines (424 loc) 20.5 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.createAdminRouter = void 0; const express_1 = require("express"); const dashboard_1 = require("./views/dashboard"); const users_1 = require("./views/users"); const roles_1 = require("./views/roles"); const features_1 = require("./views/features"); const permissions_1 = require("./views/permissions"); const createAdminRouter = (dbAdapter) => { const router = (0, express_1.Router)(); router.get('/', (req, res) => __awaiter(void 0, void 0, void 0, function* () { try { const stats = yield dbAdapter.getDashboardStats(); res.send((0, dashboard_1.getDashboardView)(stats)); } catch (error) { const stats = { users: 0, roles: 0, features: 0, permissions: 5 }; res.send((0, dashboard_1.getDashboardView)(stats)); } })); router.get('/api/stats', (req, res) => __awaiter(void 0, void 0, void 0, function* () { try { const stats = yield dbAdapter.getDashboardStats(); res.json(Object.assign(Object.assign({}, stats), { timestamp: new Date().toISOString() })); } catch (error) { res.status(500).json({ error: 'Failed to fetch stats', message: error.message }); } })); router.get('/users', (req, res) => __awaiter(void 0, void 0, void 0, function* () { try { const page = parseInt(req.query.page) || 1; const limit = parseInt(req.query.limit) || 10; const search = req.query.search || ''; const skip = (page - 1) * limit; const usersResult = yield dbAdapter.getAllUsers(limit, skip, search); const rolesResult = yield dbAdapter.getAllRoles(); const pagination = { currentPage: page, totalPages: Math.ceil(usersResult.total / limit), totalUsers: usersResult.total, hasNext: page < Math.ceil(usersResult.total / limit), hasPrev: page > 1, limit, search }; res.send((0, users_1.getUsersListView)(usersResult.items, rolesResult.items, pagination)); } catch (error) { res.status(500).send('Error loading users: ' + error.message); } })); router.get('/users/:userId', (req, res) => __awaiter(void 0, void 0, void 0, function* () { try { const user = yield dbAdapter.findUserByUserIdWithRole(req.params.userId); if (!user) { return res.status(404).send('User not found'); } const rolesResult = yield dbAdapter.getAllRoles(); res.send((0, users_1.getUserDetailsView)(user, rolesResult.items)); } catch (error) { res.status(500).send('Error loading user: ' + error.message); } })); router.post('/users/create', (req, res) => __awaiter(void 0, void 0, void 0, function* () { try { const { user_id, name, email } = req.body; const existingUser = yield dbAdapter.findUserByUserId(user_id); if (existingUser) { return res.status(400).json({ error: 'User already exists' }); } yield dbAdapter.createUser({ user_id, name, email }); res.redirect('/rbac-admin/users'); } catch (error) { res.status(500).json({ error: error.message }); } })); router.post('/users/:userId/update', (req, res) => __awaiter(void 0, void 0, void 0, function* () { try { const { name, email } = req.body; yield dbAdapter.updateUser(req.params.userId, { name, email }); res.redirect(`/rbac-admin/users/${req.params.userId}`); } catch (error) { res.status(500).json({ error: error.message }); } })); router.post('/users/:userId/assign-role', (req, res) => __awaiter(void 0, void 0, void 0, function* () { try { const { roleName } = req.body; const user = yield dbAdapter.findUserByUserId(req.params.userId); if (!user) { return res.status(404).json({ error: 'User not found' }); } if (roleName) { const role = yield dbAdapter.findRoleByName(roleName); if (!role) { return res.status(404).json({ error: 'Role not found' }); } // Handle both MongoDB (_id) and PostgreSQL (id) const roleId = role._id || role.id; yield dbAdapter.updateUser(req.params.userId, { role_id: roleId }); } else { return res.status(404).json({ error: 'Role not found' }); } res.redirect(req.get('Referer') || '/rbac-admin/users'); } catch (error) { res.status(500).json({ error: error.message }); } })); router.post('/users/:userId/delete', (req, res) => __awaiter(void 0, void 0, void 0, function* () { try { yield dbAdapter.deleteUser(req.params.userId); res.json({ message: 'User deleted successfully' }); } catch (error) { res.status(500).json({ error: error.message }); } })); router.get('/roles', (req, res) => __awaiter(void 0, void 0, void 0, function* () { try { const rolesResult = yield dbAdapter.getAllRoles(); const featuresResult = yield dbAdapter.getAllFeatures(); const permissionsResult = yield dbAdapter.getAllPermissions(); res.send((0, roles_1.getRolesListView)(rolesResult.items, featuresResult.items, permissionsResult.items)); } catch (error) { res.status(500).send('Error loading roles: ' + error.message); } })); router.get('/roles/:roleId', (req, res) => __awaiter(void 0, void 0, void 0, function* () { try { const role = yield dbAdapter.findRoleByIdWithFeatures(req.params.roleId); if (!role) { return res.status(404).send('Role not found'); } const featuresResult = yield dbAdapter.getAllFeatures(); const permissionsResult = yield dbAdapter.getAllPermissions(); res.send((0, roles_1.getRoleDetailsView)(role, featuresResult.items, permissionsResult.items)); } catch (error) { res.status(500).send('Error loading role: ' + error.message); } })); router.post('/roles/create', (req, res) => __awaiter(void 0, void 0, void 0, function* () { try { const { name, description, features } = req.body; const existingRole = yield dbAdapter.findRoleByName(name); if (existingRole) { return res.status(400).json({ error: 'Role already exists' }); } const newRole = yield dbAdapter.createRole({ name, description }); // If features are provided, assign them to the role if (features && features.length > 0) { const roleId = newRole._id || newRole.id; const featurePermissions = features.map((f) => ({ feature_id: f.feature, permission_ids: f.permissions })); yield dbAdapter.assignRoleFeaturePermissions(roleId, featurePermissions); } res.json({ success: true, message: 'Role created successfully' }); } catch (error) { res.status(500).json({ error: error.message }); } })); router.post('/roles/:roleId/delete', (req, res) => __awaiter(void 0, void 0, void 0, function* () { try { yield dbAdapter.deleteRole(req.params.roleId); res.json({ success: true, message: 'Role deleted successfully' }); } catch (error) { res.status(500).json({ error: error.message }); } })); // TODO: Implement complex role management routes for both MongoDB and PostgreSQL // These routes need specialized implementation for role-feature-permission relationships router.post('/roles/:roleId/assign-features', (req, res) => __awaiter(void 0, void 0, void 0, function* () { try { let { featurePermissions, featureIds } = req.body; // Handle form data: convert featureIds to featurePermissions with all permissions if (!featurePermissions && featureIds) { const featureIdArray = Array.isArray(featureIds) ? featureIds : [featureIds]; const allPermissions = yield dbAdapter.getAllPermissions(); featurePermissions = featureIdArray.map(featureId => ({ feature_id: featureId, permission_ids: allPermissions.items.map(p => p._id || p.id) })); } if (!featurePermissions || featurePermissions.length === 0) { return res.status(400).json({ error: 'No features or permissions provided' }); } yield dbAdapter.assignRoleFeaturePermissions(req.params.roleId, featurePermissions); res.redirect(`/rbac-admin/roles/${req.params.roleId}`); } catch (error) { res.status(500).json({ error: error.message }); } })); // Remove permissions from a specific feature within a role router.post('/roles/:roleId/remove-permissions', (req, res) => __awaiter(void 0, void 0, void 0, function* () { var _a; try { const { featureIds, permissionIds } = req.body; const featureId = Array.isArray(featureIds) ? featureIds[0] : featureIds; const permissionsToRemove = Array.isArray(permissionIds) ? permissionIds : [permissionIds]; // Get current role features const role = yield dbAdapter.findRoleByIdWithFeatures(req.params.roleId); if (!role) { return res.status(404).json({ error: 'Role not found' }); } // Find the existing feature assignment const existingFeature = (_a = role.features) === null || _a === void 0 ? void 0 : _a.find((f) => { var _a, _b, _c, _d; const fId = ((_a = f.feature_id) === null || _a === void 0 ? void 0 : _a.toString()) || ((_c = (_b = f.feature) === null || _b === void 0 ? void 0 : _b._id) === null || _c === void 0 ? void 0 : _c.toString()) || ((_d = f._id) === null || _d === void 0 ? void 0 : _d.toString()); return fId === featureId; }); if (!existingFeature || !existingFeature.permissions) { return res.status(404).json({ error: 'Feature or permissions not found' }); } // Remove specified permissions const existingPermissionIds = Array.isArray(existingFeature.permissions) ? existingFeature.permissions.map((p) => (p._id || p.id || p).toString()) : []; const updatedPermissions = existingPermissionIds.filter((pId) => !permissionsToRemove.includes(pId)); const featurePermissions = [{ feature_id: featureId, permission_ids: updatedPermissions }]; yield dbAdapter.assignRoleFeaturePermissions(req.params.roleId, featurePermissions); res.json({ success: true, message: 'Permission removed successfully' }); } catch (error) { res.status(500).json({ error: error.message }); } })); // Add permissions to a specific feature within a role router.post('/roles/:roleId/add-permissions', (req, res) => __awaiter(void 0, void 0, void 0, function* () { var _a; try { const { featureIds, permissionIds } = req.body; const featureId = Array.isArray(featureIds) ? featureIds[0] : featureIds; const permissions = Array.isArray(permissionIds) ? permissionIds : [permissionIds]; // Get current role features to merge with new permissions const role = yield dbAdapter.findRoleByIdWithFeatures(req.params.roleId); if (!role) { return res.status(404).json({ error: 'Role not found' }); } // Find the existing feature assignment (handle undefined features array) const existingFeature = (_a = role.features) === null || _a === void 0 ? void 0 : _a.find((f) => { var _a, _b, _c, _d; const fId = ((_a = f.feature_id) === null || _a === void 0 ? void 0 : _a.toString()) || ((_c = (_b = f.feature) === null || _b === void 0 ? void 0 : _b._id) === null || _c === void 0 ? void 0 : _c.toString()) || ((_d = f._id) === null || _d === void 0 ? void 0 : _d.toString()); return fId === featureId; }); let updatedPermissions = permissions; if (existingFeature && existingFeature.permissions) { // Merge existing permissions with new ones (handle undefined permissions array) const existingPermissionIds = Array.isArray(existingFeature.permissions) ? existingFeature.permissions.map((p) => (p._id || p.id || p).toString()) : []; updatedPermissions = [...new Set([...existingPermissionIds, ...permissions])]; } const featurePermissions = [{ feature_id: featureId, permission_ids: updatedPermissions }]; yield dbAdapter.assignRoleFeaturePermissions(req.params.roleId, featurePermissions); res.redirect(`/rbac-admin/roles/${req.params.roleId}`); } catch (error) { res.status(500).json({ error: error.message }); } })); router.get('/features', (req, res) => __awaiter(void 0, void 0, void 0, function* () { try { const featuresResult = yield dbAdapter.getAllFeatures(); res.send((0, features_1.getFeaturesListView)(featuresResult.items)); } catch (error) { res.status(500).send('Error loading features: ' + error.message); } })); router.get('/features/:featureId', (req, res) => __awaiter(void 0, void 0, void 0, function* () { try { const feature = yield dbAdapter.findFeatureById(req.params.featureId); if (!feature) { return res.status(404).send('Feature not found'); } const rolesResult = yield dbAdapter.getAllRoles(); res.send((0, features_1.getFeatureDetailsView)(feature, rolesResult.items)); } catch (error) { res.status(500).send('Error loading feature: ' + error.message); } })); router.post('/features/create', (req, res) => __awaiter(void 0, void 0, void 0, function* () { try { const { name, description } = req.body; const existing = yield dbAdapter.findFeatureByName(name); if (existing) { return res.status(400).json({ error: 'Feature already exists' }); } yield dbAdapter.createFeature({ name, description }); res.redirect('/rbac-admin/features'); } catch (error) { res.status(500).json({ error: error.message }); } })); router.post('/features/:featureId/update', (req, res) => __awaiter(void 0, void 0, void 0, function* () { try { const { name, description } = req.body; yield dbAdapter.updateFeature(req.params.featureId, { name, description }); res.json({ success: true, message: 'Feature updated successfully' }); } catch (error) { res.status(500).json({ error: error.message }); } })); router.post('/features/:featureId/delete', (req, res) => __awaiter(void 0, void 0, void 0, function* () { try { yield dbAdapter.deleteFeature(req.params.featureId); res.json({ success: true, message: 'Feature deleted successfully' }); } catch (error) { res.status(500).json({ error: error.message }); } })); router.get('/permissions', (req, res) => __awaiter(void 0, void 0, void 0, function* () { try { const permissionsResult = yield dbAdapter.getAllPermissions(); res.send((0, permissions_1.getPermissionsListView)(permissionsResult.items)); } catch (error) { res.status(500).send('Error loading permissions: ' + error.message); } })); router.get('/permissions/:permissionId', (req, res) => __awaiter(void 0, void 0, void 0, function* () { try { const permission = yield dbAdapter.findPermissionById(req.params.permissionId); if (!permission) { return res.status(404).send('Permission not found'); } const rolesResult = yield dbAdapter.getAllRoles(); res.send((0, permissions_1.getPermissionDetailsView)(permission, rolesResult.items)); } catch (error) { res.status(500).send('Error loading permission: ' + error.message); } })); router.post('/permissions/create', (req, res) => __awaiter(void 0, void 0, void 0, function* () { try { const { name, description } = req.body; const existingPermission = yield dbAdapter.findPermissionByName(name); if (existingPermission) { return res.status(400).json({ error: 'Permission already exists' }); } yield dbAdapter.createPermission({ name, description }); res.redirect('/rbac-admin/permissions'); } catch (error) { res.status(500).json({ error: error.message }); } })); router.post('/permissions/create-standard', (req, res) => __awaiter(void 0, void 0, void 0, function* () { try { const { permissions } = req.body; const createdPermissions = []; for (const perm of permissions) { const existingPermission = yield dbAdapter.findPermissionByName(perm.name); if (!existingPermission) { const created = yield dbAdapter.createPermission(perm); createdPermissions.push(created); } } res.json({ message: `Created ${createdPermissions.length} standard permissions`, permissions: createdPermissions }); } catch (error) { res.status(500).json({ error: error.message }); } })); router.post('/permissions/:permissionId/update', (req, res) => __awaiter(void 0, void 0, void 0, function* () { try { const { name, description } = req.body; yield dbAdapter.updatePermission(req.params.permissionId, { name, description }); res.json({ message: 'Permission updated successfully' }); } catch (error) { res.status(500).json({ error: error.message }); } })); router.post('/permissions/:permissionId/delete', (req, res) => __awaiter(void 0, void 0, void 0, function* () { try { yield dbAdapter.deletePermission(req.params.permissionId); res.json({ message: 'Permission deleted successfully' }); } catch (error) { res.status(500).json({ error: error.message }); } })); return router; }; exports.createAdminRouter = createAdminRouter;