From fa96ed5976b649c2e15efd30418411fa4fdc16b5 Mon Sep 17 00:00:00 2001 From: "manuel.sowada" Date: Sun, 26 Apr 2026 07:39:30 +0000 Subject: [PATCH] rbac outsourced --- server.js | 6 + src/routes/indexRoutes.js | 4 +- src/services/authenticationManager.js | 168 ++++++++++++-------------- src/services/rbacManager.js | 116 ++++++++++++++++++ utils.js | 4 +- 5 files changed, 203 insertions(+), 95 deletions(-) create mode 100644 src/services/rbacManager.js diff --git a/server.js b/server.js index ea2e62d..6735bc5 100644 --- a/server.js +++ b/server.js @@ -98,6 +98,7 @@ const server = https.createServer(httpsOptions, app); let AuthenticationManager = require(`@services/authenticationManager.js`); let ActiveDirectory = require(`@services/activeDirectoryManager.js`); let VaultifyManager = require(`@services/vaultifyManager.js`); + let RBACManager = require(`@services/rbacManager.js`); service.set('socketManager', new SocketManager(io)); await service.get('socketManager').addAsync('/'); @@ -133,7 +134,9 @@ const server = https.createServer(httpsOptions, app); databaseModel.set('permissionModel', require(`@models/permissionModel`)(service.get('sqlManager').getInstance('main'))); databaseModel.set('roleModel', require(`@models/roleModel`)(service.get('sqlManager').getInstance('main'))); databaseModel.set('rolePermissionsModel', require(`@models/rolePermissionsModel`)(service.get('sqlManager').getInstance('main'))); + service.set('authenticationManager', new AuthenticationManager(databaseModel.get('authentication'), app.locals.configuration.integration.token.secret, databaseModel)); + service.set('rbacManager', new RBACManager(databaseModel)); service.set('activeDirectoryManager', new ActiveDirectory(app.locals.configuration.integration.activedirectory)) @@ -210,6 +213,7 @@ const server = https.createServer(httpsOptions, app); service.get('eventManager').write(null, plugin.levelId, null, `${plugin.pluginName} v${plugin.metadata.version} ${plugin.message}`); }); + //#region Menu-Generator app.use(async (req, res, next) => { next(); @@ -223,6 +227,8 @@ const server = https.createServer(httpsOptions, app); require(`${app.locals.path.source}/routes/adminRoutes.js`).route(app, service); // #3 - token security always enabled //#endregion + app.use(service.get('authenticationManager').authenticate()); + app.use(service.get('authenticationManager').requirePermissionMiddleware()); //#region Implements sockets require(`${app.locals.path.source}/sockets/mainSocket.js`)( diff --git a/src/routes/indexRoutes.js b/src/routes/indexRoutes.js index 04dea15..3fbc4e2 100644 --- a/src/routes/indexRoutes.js +++ b/src/routes/indexRoutes.js @@ -11,10 +11,8 @@ const { doesNotReject } = require('assert'); module.exports = { route: function(app, service) { - app.use(service.get('authenticationManager').authenticate()); - app.get('/', async (req, res) => { - const startMenuItems = await global.startMenuItems(app, req.cookies.ObjectGUID, false); + const startMenuItems = await global.startMenuItems(app, req.cookies.ObjectGUID, true); res.render('desktop', { layout: 'default', startMenuItems: startMenuItems }); }); diff --git a/src/services/authenticationManager.js b/src/services/authenticationManager.js index 8b470dd..8b5dbbd 100644 --- a/src/services/authenticationManager.js +++ b/src/services/authenticationManager.js @@ -2,10 +2,10 @@ const jwt = require('jsonwebtoken'); const bcrypt = require('bcryptjs'); class AuthenticationManager { - constructor(model, secretKey, databaseModel) { + constructor(model, secretKey, rbacService) { this.Authentication = model; this.SECRET_KEY = secretKey; - this.databaseModel = databaseModel; + this.rbac = rbacService; } // ========================================================= @@ -36,79 +36,7 @@ class AuthenticationManager { } // ========================================================= - // RBAC RESOLVER (LIVE - WICHTIG!) - // ========================================================= - -async resolvePermissions(objectGuid) { - const AuthenticationGroups = this.databaseModel.get('authenticationGroupsModel'); - const GroupClosure = this.databaseModel.get('groupClosureModel'); - const AuthenticationRoles = this.databaseModel.get('authenticationRolesModel'); - const GroupRoles = this.databaseModel.get('groupRolesModel'); - const RolePermissions = this.databaseModel.get('rolePermissionsModel'); - const Permission = this.databaseModel.get('permissionModel'); - - // 1. USER GROUPS - const userGroups = await AuthenticationGroups.findAll({ - where: { Authentication_ObjectGUID: objectGuid } - }); - - const directGroupIds = userGroups.map(g => g.Group_ObjectGUID); - - // 2. NESTED GROUPS - let allGroupIds = [...directGroupIds]; - - if (directGroupIds.length) { - const closure = await GroupClosure.findAll({ - where: { ParentGroup_ObjectGUID: directGroupIds } - }); - - allGroupIds.push(...closure.map(c => c.ChildGroup_ObjectGUID)); - } - - allGroupIds = [...new Set(allGroupIds)]; - - // 3. ROLES - const userRoles = await AuthenticationRoles.findAll({ - where: { Authentication_ObjectGUID: objectGuid } - }); - - const groupRoles = await GroupRoles.findAll({ - where: { Group_ObjectGUID: allGroupIds } - }); - - const roleIds = [ - ...new Set([ - ...userRoles.map(r => r.Role_ID), - ...groupRoles.map(r => r.Role_ID) - ]) - ]; - - // 4. PERMISSIONS - const rolePerms = await RolePermissions.findAll({ - where: { Role_ID: roleIds } - }); - - const permissionIds = rolePerms.map(r => r.Permission_ID); - - const permissions = await Permission.findAll({ - where: { ID: permissionIds } - }); - - // 🔥 HIER DIE ÄNDERUNG - return { - groups: allGroupIds, - roles: roleIds, - permissions: permissions.map(p => ({ - id: p.ID, - scope: p.Scope, - resource: p.Resource, - action: p.Action - })) - }; -} - - // ========================================================= - // LOGIN (minimal change) + // LOGIN // ========================================================= async login(sAMAccountName, password) { @@ -142,11 +70,7 @@ async resolvePermissions(objectGuid) { user.online = true; await user.save(); - return { - token, - levelId: 0, - message: 'Erfolgreich angemeldet' - }; + return { token, levelId: 0, message: 'Erfolgreich angemeldet' }; } // ========================================================= @@ -168,7 +92,7 @@ async resolvePermissions(objectGuid) { } // ========================================================= - // VERIFY TOKEN (unchanged) + // VERIFY TOKEN // ========================================================= async verifyUserToken(sAMAccountName) { @@ -192,22 +116,36 @@ async resolvePermissions(objectGuid) { } // ========================================================= - // 🔥 AUTH MIDDLEWARE (HIER PASSIERT DIE MAGIE) + // 🔥 MIDDLEWARE BLEIBT HIER // ========================================================= - authenticate() { return async (req, res, next) => { try { - // 🔥 SKIP PUBLIC ROUTES - if ( - req.path.startsWith('/login') || - req.path.startsWith('/public') - ) { + // ===================================================== + // 🔥 GLOBAL PUBLIC ROUTE BYPASS (ROBUST) + // ===================================================== + + const url = req.originalUrl.split('?')[0]; + + const publicRoutes = [ + '/login', + '/public' + ]; + + const isPublicRoute = publicRoutes.some(route => + url === route || url.startsWith(route + '/') + ); + + if (isPublicRoute) { return next(); } + // ===================================================== + // 🔐 AUTH FLOW + // ===================================================== + const sAMAccountName = req.cookies?.sAMAccountName; if (!sAMAccountName) { @@ -228,14 +166,18 @@ async resolvePermissions(objectGuid) { return res.redirect('/login'); } - const rbac = await this.resolvePermissions(user.ObjectGUID); + const rbac = await this.rbac.resolvePermissions(user.ObjectGUID); + + const normalized = this.rbac.normalize(rbac.permissions); + const isSuperAdmin = this.rbac.isSuperAdmin(normalized); req.user = { ...user.toJSON(), jwt: payload, groups: rbac.groups, roles: rbac.roles, - permissions: rbac.permissions + permissions: normalized, + isSuperAdmin }; next(); @@ -246,6 +188,52 @@ async resolvePermissions(objectGuid) { } }; } + + // ========================================================= + // 🔐 GLOBAL RBAC MIDDLEWARE (app.use) + // ========================================================= + // + // USAGE: + // app.get('/admin/users', (req, res) => { + // if (!req.auth.hasPermission([ + // { scope: 'USER', action: 'READ', resource: 'USERS' } + // ])) { + // return res.status(403).send('Forbidden'); + // } + + // res.json({ ok: true }); + // }); + requirePermissionMiddleware() { + return async (req, res, next) => { + try { + + // 🔥 wenn noch kein User da ist → Auth Middleware fehlt + if (!req.user) { + return next(); // oder 401 wenn du streng sein willst + } + + const rbac = this.rbac; + + const permissions = req.user.permissions || []; + const isSuperAdmin = req.user.isSuperAdmin || false; + + req.auth = { + permissions, + isSuperAdmin, + hasPermission: (required) => + rbac.hasPermission(permissions, required, isSuperAdmin) + }; + + return next(); + + next(); + + } catch (err) { + console.error('[RBAC MIDDLEWARE ERROR]', err); + return res.status(500).json({ message: 'RBAC Fehler' }); + } + }; + } } module.exports = AuthenticationManager; \ No newline at end of file diff --git a/src/services/rbacManager.js b/src/services/rbacManager.js new file mode 100644 index 0000000..2494753 --- /dev/null +++ b/src/services/rbacManager.js @@ -0,0 +1,116 @@ +// rbac/RbacService.js + +class RBACManager { + constructor(databaseModel) { + this.db = databaseModel; + } + + async resolvePermissions(objectGuid) { + const AuthenticationGroups = this.db.get('authenticationGroupsModel'); + const GroupClosure = this.db.get('groupClosureModel'); + const AuthenticationRoles = this.db.get('authenticationRolesModel'); + const GroupRoles = this.db.get('groupRolesModel'); + const RolePermissions = this.db.get('rolePermissionsModel'); + const Permission = this.db.get('permissionModel'); + + // 1. USER GROUPS + const userGroups = await AuthenticationGroups.findAll({ + where: { Authentication_ObjectGUID: objectGuid } + }); + + const directGroupIds = userGroups.map(g => g.Group_ObjectGUID); + + // 2. NESTED GROUPS + let allGroupIds = [...directGroupIds]; + + if (directGroupIds.length) { + const closure = await GroupClosure.findAll({ + where: { ParentGroup_ObjectGUID: directGroupIds } + }); + + allGroupIds.push(...closure.map(c => c.ChildGroup_ObjectGUID)); + } + + allGroupIds = [...new Set(allGroupIds)]; + + // 3. ROLES + const userRoles = await AuthenticationRoles.findAll({ + where: { Authentication_ObjectGUID: objectGuid } + }); + + const groupRoles = await GroupRoles.findAll({ + where: { Group_ObjectGUID: allGroupIds } + }); + + const roleIds = [ + ...new Set([ + ...userRoles.map(r => r.Role_ID), + ...groupRoles.map(r => r.Role_ID) + ]) + ]; + + // 4. PERMISSIONS + const rolePerms = await RolePermissions.findAll({ + where: { Role_ID: roleIds } + }); + + const permissionIds = rolePerms.map(r => r.Permission_ID); + + const permissions = await Permission.findAll({ + where: { ID: permissionIds } + }); + + return { + groups: allGroupIds, + roles: roleIds, + permissions: permissions.map(p => ({ + id: p.ID, + scope: p.Scope, + resource: p.Resource, + action: p.Action + })) + }; + } + + // 🔥 SUPER CLEAN CHECK (wiederverwendbar überall) + isSuperAdmin(permissions) { + return permissions.some(p => + p.scope === 'SYSTEM' && + p.resource === 'ALL' && + p.action === 'ALL' + ); + } + + // 🔥 GENERIC CHECK FUNCTION (WICHTIG) + hasPermission(userPerms, requiredPerms, isSuperAdmin = false) { + if (isSuperAdmin) return true; + + return userPerms.some(userPerm => + requiredPerms.some(required => { + const scopeMatch = userPerm.scope === required.scope; + + const actionMatch = + userPerm.action === 'ALL' || + userPerm.action === required.action || + required.action === 'ALL'; + + const resourceMatch = + !userPerm.resource || + userPerm.resource === 'ALL' || + userPerm.resource === required.resource; + + return scopeMatch && actionMatch && resourceMatch; + }) + ); + } + + normalize(permissions) { + return permissions.map(p => ({ + scope: p.scope, + action: p.action, + resource: p.resource || null + })); + } +} + +module.exports = RBACManager; \ No newline at end of file diff --git a/utils.js b/utils.js index 4fa4ec5..7009de6 100644 --- a/utils.js +++ b/utils.js @@ -53,10 +53,10 @@ module.exports = startMenuItems = async function (app, objectGuid, debug = false // ========================= // Load user permissions // ========================= - const authManager = service.get('authenticationManager'); + const rbacManager = service.get('rbacManager'); const userPermissions = - (await authManager.resolvePermissions(objectGuid)) + (await rbacManager.resolvePermissions(objectGuid)) ?.permissions || []; const normalizedPermissions = userPermissions.map(p => ({