@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
JavaScript
;
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;