From f09f148aea2fd05bfef6822288d5a506a0a33118 Mon Sep 17 00:00:00 2001 From: root Date: Fri, 1 May 2026 22:37:21 +0200 Subject: [PATCH] rbac and licenses --- dbcreate.sql | 65 +++++++---- public/javascript/os.js | 12 +- public/javascript/rbacAPI.js | 139 +++++++++++++++++++++-- public/styles/os.css | 6 +- public/views/rbac.hbs | 25 +++- server.js | 3 + src/models/PermissionTraceView.js | 44 +++++++ src/models/integratedStartMenuItems.json | 8 ++ src/models/vaultModel.js | 41 +++---- src/routes/adminRoutes.js | 12 +- src/services/pluginManager.js | 4 +- src/services/rbacManager.js | 6 + src/services/vaultifyManager.js | 24 +++- utils.js | 17 ++- 14 files changed, 326 insertions(+), 80 deletions(-) create mode 100644 src/models/PermissionTraceView.js diff --git a/dbcreate.sql b/dbcreate.sql index 977e1f7..6757c55 100644 --- a/dbcreate.sql +++ b/dbcreate.sql @@ -44,26 +44,27 @@ GO -- DROP TABLE IF EXISTS dbo.ObjectSource; -- DROP TABLE IF EXISTS dbo.AuthenticationUAC; -- DROP TABLE IF EXISTS dbo.Vault; -GO +-- GO /* ========================================================= CORE TABLES ========================================================= */ CREATE TABLE dbo.Vault ( - ID int IDENTITY(1,1) NOT NULL, - CustomerGUID uniqueidentifier NOT NULL, - Feature nvarchar(128) COLLATE SQL_Latin1_General_CP1_CI_AS NOT NULL, - Payload nvarchar(MAX) COLLATE SQL_Latin1_General_CP1_CI_AS NOT NULL, - Signature nvarchar(MAX) COLLATE SQL_Latin1_General_CP1_CI_AS NOT NULL, - Active bit DEFAULT 1 NOT NULL, - ExpiresAt datetime NULL, - CreatedAt datetime DEFAULT getdate() NOT NULL, - UpdatedAt datetime DEFAULT getdate() NULL, - CONSTRAINT PK__Vault__3214EC275180843D PRIMARY KEY (ID) + ID INT IDENTITY(1,1) NOT NULL, + License_ID INT NOT NULL, + Customer_ID INT NOT NULL, + Signature NVARCHAR(512) NOT NULL, + EncryptedPayload VARBINARY(MAX) NOT NULL, + ExpiresAt DATETIME2 NULL, + Status_ID TINYINT NOT NULL, + LastVerifiedAt DATETIME2 NULL, + CreateDate DATETIME2 NOT NULL + CONSTRAINT DF_Vault_CreateDate DEFAULT SYSDATETIME(), + CONSTRAINT PK_Vault PRIMARY KEY (ID), + CONSTRAINT CK_Vault_Status CHECK (Status_ID IN (0,1,2,3,4)) ); - CREATE TABLE dbo.ObjectSource ( ID INT IDENTITY(1,1) PRIMARY KEY, Name VARCHAR(100) NOT NULL UNIQUE @@ -371,9 +372,10 @@ INSERT INTO dbo.EventLevels VALUES INSERT INTO dbo.[Role] (Name,Description,RoleType) VALUES ('ADMIN','System Administrators','SYSTEM'); -INSERT INTO dbo.Permission (Scope,Resource,Action) -VALUES ('SYSTEM','ALL','ALL'); +INSERT INTO dbo.Permission (Scope,Resource,Action) VALUES +('SYSTEM','ALL','ALL') +('SYSTEM','ALL','Default_Access') INSERT INTO dbo.RolePermissions SELECT r.ID, p.ID @@ -382,6 +384,10 @@ JOIN dbo.Permission p ON p.Scope='SYSTEM' WHERE r.Name='ADMIN'; +INSERT INTO dbo.Group (ObjectGUID,Name,ObjectSource_ID) VALUES +('00000000-0000-0000-0000-000000000001','ADMINISTRATORS',1) +('00000000-0000-0000-0000-000000000002','USERS',1); + /* ========================================================= ADMIN USER ========================================================= */ @@ -499,19 +505,28 @@ SELECT r.Description, r.RoleType, - COUNT(DISTINCT gr.Group_ObjectGUID) AS GroupCount, - COUNT(DISTINCT vau.ObjectGUID) AS UserCount + ISNULL(g.GroupCount, 0) AS GroupCount, + ISNULL(u.UserCount, 0) AS UserCount -FROM dbo.Role AS r +FROM dbo.Role r -LEFT JOIN dbo.vAuthenticationRoles AS vau - ON vau.Role_ID = r.ID +-- 👥 Gruppen zählen +LEFT JOIN ( + SELECT + Role_ID, + COUNT(DISTINCT Group_ObjectGUID) AS GroupCount + FROM dbo.GroupRoles + GROUP BY Role_ID +) g ON g.Role_ID = r.ID -LEFT JOIN dbo.GroupRoles AS gr - ON gr.Role_ID = r.ID - -GROUP BY - r.ID, r.Name, r.Description, r.RoleType; +-- 👤 NUR direkte User zählen (WICHTIG) +LEFT JOIN ( + SELECT + Role_ID, + COUNT(DISTINCT Authentication_ObjectGUID) AS UserCount + FROM dbo.AuthenticationRoles + GROUP BY Role_ID +) u ON u.Role_ID = r.ID; GO @@ -606,7 +621,7 @@ FROM dbo.ObjectSource RIGHT OUTER JOIN dbo.AuthenticationGroups AS ag ON dbo.[Group].ObjectGUID = ag.Group_ObjectGUID LEFT OUTER JOIN dbo.GroupRoles AS gr ON dbo.[Group].ObjectGUID = gr.Group_ObjectGUID GROUP BY dbo.[Group].ObjectGUID, dbo.[Group].Name, dbo.ObjectSource.Name, dbo.[Group].distinguishedName - +GO -- ======================================================== -- 9. BONUS: PERMISSION TRACE (WHY DOES USER HAVE THIS?) diff --git a/public/javascript/os.js b/public/javascript/os.js index 3e37fca..df20443 100644 --- a/public/javascript/os.js +++ b/public/javascript/os.js @@ -18,7 +18,7 @@ const ctx = new ContextMenu(); const windowCleanup = new Map(); const username = getCookie('sAMAccountName'); const LS_KEY = (key) => `${username}:${key}`; -const MAX_PADDING = { left: 4, top: 4, right: 4, bottom: (56 - 4) }; +let MAX_PADDING = { left: 4, top: 4, right: 4, bottom: 40 }; startBtn.addEventListener('click', (evt) => { evt.stopPropagation(); // verhindert sofortiges Schließen @@ -28,13 +28,19 @@ startBtn.addEventListener('click', (evt) => { function updateStartMenuPosition() { const height = taskbar.offsetHeight; - startMenu.style.bottom = (height + 5) + 'px'; + startMenu.style.bottom = (height + 12) + 'px'; } const observer = new ResizeObserver(entries => { for (let entry of entries) { const height = entry.contentRect.height; - document.documentElement.style.setProperty('--auto-taskbar-height', height + 'px'); + MAX_PADDING = { left: 4, top: 4, right: 4, bottom: height + 8 }; + + windowsContainer.querySelectorAll('[data-winid]').forEach(win => { + if(win.dataset.state === 'maximized') { + applyMaximized(win) ; + } + }); } }); diff --git a/public/javascript/rbacAPI.js b/public/javascript/rbacAPI.js index 163e168..dc118a8 100644 --- a/public/javascript/rbacAPI.js +++ b/public/javascript/rbacAPI.js @@ -42,6 +42,11 @@ const RBAC = { createRole: (name) => api('/api/rbac/role/create', 'POST', { name }), deleteRole: (id) => api(`/api/rbac/role/${id}`, 'DELETE'), + // 🎭 PERMISSIONS + loadPermissions: () => api('/api/rbac/permission/get', 'POST'), + createPermission: (data) => api('/api/rbac/permission/create', 'POST', data), + deletePermission: (id) => api(`/api/rbac/permission/${id}`, 'DELETE'), + // 🔗 ASSIGNMENTS addUserToGroup: (authGuid, groupGuid) => api('/api/rbac/group/add-user', 'POST', { authGuid, groupGuid }), addUserToRole: (authGuid, roleId) => api('/api/rbac/role/add-user', 'POST', { authGuid, roleId }), @@ -115,7 +120,7 @@ function createDropZone(el, type, target) { // 📋 TABLE (USERS) ////////////////////////////// -const rbacVT = virtualTable({ +const rbacUsersVT = virtualTable({ tableEl: document.querySelector('#rbacUsersTable'), data: [], rowHeight: 20, @@ -177,13 +182,81 @@ const rbacVT = virtualTable({ } }); + + +////////////////////////////// +// 📋 TABLE (PERMISSIONS) +////////////////////////////// + +const rbacPermissionsVT = virtualTable({ + tableEl: document.querySelector('#rbacPermissionsTable'), + data: [], + rowHeight: 20, + buffer: 5, + groupKey: 'Scope', + rowKey: 'ID', + filterConfig: { + hideCounter: true, + exceptedColumns: ['', 'ID'] + }, + + customRender: (row, tr) => { + + createDragZone(tr, row, 'user'); + + tr.addEventListener('contextmenu', (evt) => { + evt.preventDefault(); + + ctx.setItems([ + { + label: "Details", + onClick: () => showAuthDetails(row.ObjectGUID) + } + ]); + + ctx.show(evt.pageX + 5, { y: evt.pageY + 5 }); + }); + + createTd(tr, + ``, { + styles: { + 'position': 'sticky', + 'left': '0px', + 'max-width': '20px', + 'z-index': '2' + }, classes: [ + 'text-align:left' + ], onclick: () => { + if(row['ID'] === 1) return; + deletePermission(row['ID'], `${row['Scope']}.${row['Resource']}.${row['Action']}`); + } + }); + createTd(tr, row['ID'], { classes: [ 'text-align:left' ], styles: { 'max-width': '100px' } } ); + createTd(tr, row['Scope'], { classes: [ 'text-align:left' ] }); + createTd(tr, row['Resource'], { classes: [ 'text-align:center' ] }); + createTd(tr, row['Action'], { classes: [ 'text-align:center' ] }); + } +}); + ////////////////////////////// // 📥 LOADERS ////////////////////////////// async function loadUsers() { try { - rbacVT.source(await RBAC.loadUsers()); + rbacUsersVT.source(await RBAC.loadUsers()); + } catch (err) { + console.error(err); + } +} + +async function loadPermissions() { + try { + rbacPermissionsVT.source(await RBAC.loadPermissions()); } catch (err) { console.error(err); } @@ -273,6 +346,7 @@ async function loadRoles() { } + ////////////////////////////// // 👤 USER ACTIONS ////////////////////////////// @@ -307,6 +381,9 @@ async function deleteUser(guid, name) { const user = await RBAC.deleteUser(guid); sendUserEvent('RBAC', `Benutzer ${user.sAMAccountName || ''} [${user.ObjectGUID}] gelöscht`, null, 0); loadUsers(); + loadGroups(); + loadRoles(); + loadPermissions(); } } } @@ -331,6 +408,8 @@ async function deleteGroup(guid, name) { sendUserEvent('RBAC', `Du hast die Gruppe ${name || ''} [${group.ObjectGUID}] gelöscht`, null, 0); loadUsers(); loadGroups(); + loadRoles(); + loadPermissions(); } } } @@ -369,19 +448,65 @@ async function deleteRole(id, name) { sendUserEvent('RBAC', `Rolle ${name} gelöscht`, null, 0); + loadUsers(); + loadGroups(); loadRoles(); - loadUsers(); // optional, falls RoleCount betroffen + loadPermissions(); // optional, falls RoleCount betroffen } } } }); } + +////////////////////////////// +// 🎭 PERMISSION ACTIONS +////////////////////////////// + +async function createPermission() { + const scope = document.getElementById('permScope').value; + const resource = document.getElementById('permResource').value; + const action = document.getElementById('permAction').value; + + if (!scope || !resource || !action) return; + + + const permission = await RBAC.createPermission( { scope, resource, action } ); + + sendUserEvent('RBAC', `Berechtigung ${scope}.${resource}.${action} angelegt`, null, 0); + loadPermissions(); +} + + +async function deletePermission(id, name) { + feedbox({ + title: `Berechtigung löschen`, + message: `Möchtest du die Berechtigung ${name} wirklich löschen`, + buttons: { + no: { text: 'Nein' }, + yes: { + text: 'Ja', + onClick: async () => { + await RBAC.deletePermission(id); + + sendUserEvent('RBAC', `Berechtigung ${name} gelöscht`, null, 0); + + loadUsers(); + loadGroups(); + loadRoles(); + loadPermissions(); + } + } + } + }); +} + + ////////////////////////////// // 🚀 INIT ////////////////////////////// -loadUsers(); -loadGroups(); -loadRoles(); - + loadUsers(); + loadGroups(); + loadRoles(); + loadPermissions(); \ No newline at end of file diff --git a/public/styles/os.css b/public/styles/os.css index 0673c33..f4bba4d 100644 --- a/public/styles/os.css +++ b/public/styles/os.css @@ -31,8 +31,8 @@ body, html { margin:0; padding:0; height:100%; overflow: hidden; font-family: va .window-resize-se { bottom: -6px;right: -6px;cursor: var(--theme-cursor-resize-270); } .window-resize-sw { bottom: -6px;left: -6px;cursor: var(--theme-cursor-resize-45); } -#taskbar { z-index: 2; position: absolute; width:100%; bottom:0; left:0; height:var(--auto-taskbar-height); overflow:visible; display:flex; flex: 0 0 auto; min-width:0; align-items:center; padding:0 8px; box-sizing:border-box; } -#start-btn { transition: background-color var(--times-transition-colors) ease; padding: 8px 12px; border-radius: 5px; border: none; margin-right:8px; } +#taskbar { z-index: 2; position: absolute; width:100%; bottom:0; left:0; height:auto; overflow:visible; display:flex; flex: 0 0 auto; min-width:0; align-items:center; padding:0 8px; box-sizing:border-box; } +#start-btn { transition: background-color var(--times-transition-colors) ease; padding: 5px 12px; border-radius: 5px; border: none; margin-right:8px; } #taskbar-windows { display:flex; gap:6px; align-items:center; flex:1; overflow-y:hidden;overflow-x: auto; min-width: 0;scrollbar-width: thin; } .taskbar-item { display: flex; position: relative; padding:4px 10px; border-radius:4px; } .taskbar-item::before { content: ''; position: absolute; bottom:1px; left:50%; width:40%; height: 4px; border-radius:4px; transform:translateX(-50%) scaleX(0); transform-origin:center; transition:transform var(--times-transition-colors) ease; } @@ -59,7 +59,7 @@ body, html { margin:0; padding:0; height:100%; overflow: hidden; font-family: va .hidden { opacity: 0; pointer-events: none; } /* Open submenu vertical */ -#start-menu { z-index: 2; transition: opacity var(--times-transition-opacity); display:flex; flex-direction:column; position: absolute; bottom: 50px; left: 8px; width: auto; min-width: 300px; border-radius: 8px; overflow: hidden; } +#start-menu { z-index: 2; transition: opacity var(--times-transition-opacity); display:flex; flex-direction:column; position: absolute; bottom: auto; left: 8px; width: auto; min-width: 300px; border-radius: 8px; overflow: hidden; } .start-header { display: flex; align-items: center; padding: 8px; font-weight: 600; } .start-header > img { height: 24px; width: 24px; margin-right: 5px; } .start-list { list-style: none; margin: 0; padding: 8px 0; height: 60vh; overflow-y: auto; } diff --git a/public/views/rbac.hbs b/public/views/rbac.hbs index d903b80..ac58ace 100644 --- a/public/views/rbac.hbs +++ b/public/views/rbac.hbs @@ -63,7 +63,7 @@ input { -
+
Users
@@ -109,13 +109,26 @@ input {
-

Permissions

- - - - + .. + +
+ + + + + + + + + + + + + +
IDScopeResourceAction
BERECHTIGUNGEN WERDEN GELADEN . . .
+
diff --git a/server.js b/server.js index 85bcfe1..f645075 100644 --- a/server.js +++ b/server.js @@ -117,6 +117,9 @@ const server = https.createServer(httpsOptions, app); databaseModel.set('authenticationOverviewView', require(`@models/authenticationOverviewView`)(service.get('sqlManager').getInstance('main'))); databaseModel.set('groupOverviewView', require(`@models/groupOverviewView`)(service.get('sqlManager').getInstance('main'))); databaseModel.set('roleOverviewView', require(`@models/roleOverviewView`)(service.get('sqlManager').getInstance('main'))); + databaseModel.set('PermissionTraceView', require(`@models/PermissionTraceView`)(service.get('sqlManager').getInstance('main'))); + + service.set('rbacManager', new RBACManager(databaseModel, runtimeFile.configuration.live.integration.token.secret)); service.set('authenticationManager', new AuthenticationManager(databaseModel.get('authentication'), runtimeFile.configuration.live.integration.token.secret)); diff --git a/src/models/PermissionTraceView.js b/src/models/PermissionTraceView.js new file mode 100644 index 0000000..c6d1bdd --- /dev/null +++ b/src/models/PermissionTraceView.js @@ -0,0 +1,44 @@ +const { DataTypes } = require('sequelize'); + +module.exports = (sequelize) => { + const PermissionTrace = sequelize.define('vPermissionTrace', { + + Authentication_ObjectGUID: { + type: DataTypes.UUID, + allowNull: false, + primaryKey: true + }, + + RoleName: { + type: DataTypes.STRING, + allowNull: true + }, + + Scope: { + type: DataTypes.STRING, + allowNull: true + }, + + Resource: { + type: DataTypes.STRING, + allowNull: true + }, + + Action: { + type: DataTypes.STRING, + allowNull: true + }, + + PermissionKey: { + type: DataTypes.STRING, + allowNull: true + } + + }, { + tableName: 'vPermissionTrace', + timestamps: false, + freezeTableName: true + }); + + return PermissionTrace; +}; \ No newline at end of file diff --git a/src/models/integratedStartMenuItems.json b/src/models/integratedStartMenuItems.json index 2ac4d0a..a8ef060 100644 --- a/src/models/integratedStartMenuItems.json +++ b/src/models/integratedStartMenuItems.json @@ -13,6 +13,7 @@ "permissions": [ { "scope": "SYSTEM", + "resource": "ALL", "action": "Administration" } ] @@ -28,6 +29,7 @@ "permissions": [ { "scope": "SYSTEM", + "resource": "ALL", "action": "Administration" } ] @@ -44,6 +46,7 @@ "permissions": [ { "scope": "SYSTEM", + "resource": "ALL", "action": "Administration" } ] @@ -69,6 +72,7 @@ "permissions": [ { "scope": "SYSTEM", + "resource": "ALL", "action": "Administration" } ] @@ -94,6 +98,7 @@ "permissions": [ { "scope": "SYSTEM", + "resource": "ALL", "action": "Administration" } ] @@ -119,6 +124,7 @@ "permissions": [ { "scope": "SYSTEM", + "resource": "ALL", "action": "Administration" } ] @@ -144,6 +150,7 @@ "permissions": [ { "scope": "SYSTEM", + "resource": "ALL", "action": "Default_Access" } ] @@ -165,6 +172,7 @@ "permissions": [ { "scope": "SYSTEM", + "resource": "ALL", "action": "Default_Access" } ] diff --git a/src/models/vaultModel.js b/src/models/vaultModel.js index d21b0b7..605fee8 100644 --- a/src/models/vaultModel.js +++ b/src/models/vaultModel.js @@ -10,29 +10,24 @@ module.exports = (sequelize) => { autoIncrement: true }, + License_ID: { + type: DataTypes.INTEGER, + allowNull: false + }, + Customer_ID: { - type: DataTypes.UUIDV4, - allowNull: false - }, - - Feature: { - type: DataTypes.STRING(128), - allowNull: false - }, - - Payload: { - type: DataTypes.TEXT, // NVARCHAR(MAX) + type: DataTypes.INTEGER, allowNull: false }, Signature: { - type: DataTypes.TEXT, + type: DataTypes.STRING(512), allowNull: false }, - Active: { - type: DataTypes.BOOLEAN, - defaultValue: true + EncryptedPayload: { + type: DataTypes.BLOB('long'), + allowNull: false }, ExpiresAt: { @@ -40,12 +35,20 @@ module.exports = (sequelize) => { allowNull: true }, - CreatedAt: { - type: DataTypes.DATE, - defaultValue: DataTypes.NOW + Status_ID: { + type: DataTypes.TINYINT, + allowNull: false, + validate: { + isIn: [[0, 1, 2, 3, 4]] + } }, - UpdatedAt: { + LastVerifiedAt: { + type: DataTypes.DATE, + allowNull: true + }, + + CreateDate: { type: DataTypes.DATE, defaultValue: DataTypes.NOW } diff --git a/src/routes/adminRoutes.js b/src/routes/adminRoutes.js index c563f95..12f81ee 100644 --- a/src/routes/adminRoutes.js +++ b/src/routes/adminRoutes.js @@ -348,8 +348,17 @@ module.exports = { // ========================================================= // 🔐 PERMISSIONS // ========================================================= + app.post('/api/rbac/permission/get', async (req, res) => { + try { + rbacPermissions = await service.get('rbacManager').getPermission(); + res.json(rbacPermissions); + } catch (err) { + res.status(500).json({ error: err.message }); + } + }); - app.post('/permission', async (req, res) => { + + app.post('/api/rbac/permission/create', async (req, res) => { try { const perm = await service.get('rbacManager').createPermission(req.body); res.json(perm); @@ -358,6 +367,7 @@ module.exports = { } }); + app.put('/permission/:id', async (req, res) => { try { await service.get('rbacManager').updatePermission(req.params.id, req.body); diff --git a/src/services/pluginManager.js b/src/services/pluginManager.js index 8767983..799edc7 100644 --- a/src/services/pluginManager.js +++ b/src/services/pluginManager.js @@ -313,8 +313,8 @@ class PluginManager { permissions: [ { scope: name, // Plugin Scope (default = plugin name) - action: "Default_Access", - resource: "MenuItem" + resource: "Plugin", + action: "Default_Access" } ] } diff --git a/src/services/rbacManager.js b/src/services/rbacManager.js index d2c5cf1..25bb2d1 100644 --- a/src/services/rbacManager.js +++ b/src/services/rbacManager.js @@ -424,6 +424,12 @@ async removeRoleFromUser(authId, roleId) { // ========================================================= // 🔐 PERMISSION CRUD // ========================================================= +async getPermission() { + const permission = this.db.get('permissionModel'); + console.log(permission) + return await permission.findAll({ raw: true }) || []; +} + async createPermission(data) { const Permission = this.db.get('permissionModel'); diff --git a/src/services/vaultifyManager.js b/src/services/vaultifyManager.js index 6e7e569..dba4833 100644 --- a/src/services/vaultifyManager.js +++ b/src/services/vaultifyManager.js @@ -75,26 +75,40 @@ class VaultifyManager { // ========================================================= verify(record) { - try { + const payload = this.parsePayload(record.Payload); + const data = { Customer_ID: record.Customer_ID, Feature: record.Feature, - Payload: this.parsePayload(record.Payload), - ExpiresAt: record.ExpiresAt + Payload: payload, + ExpiresAt: record.ExpiresAt ?? null }; const verifier = crypto.createVerify('RSA-SHA256'); - verifier.update(JSON.stringify(data)); verifier.end(); - return verifier.verify( + const isValid = verifier.verify( this.publicKey, record.Signature, 'base64' ); + if (!isValid) return false; + + // 🔥 WICHTIG: Ablaufdatum HIER erzwingen + if (payload.expiresAt && record.ExpiresAt) { + if (payload.expiresAt !== record.ExpiresAt) { + return false; + } + } + if (record.ExpiresAt && new Date(record.ExpiresAt) < new Date()) { + return false; + } + + return true; + } catch { return false; } diff --git a/utils.js b/utils.js index 759841b..46557cd 100644 --- a/utils.js +++ b/utils.js @@ -63,7 +63,6 @@ module.exports = startMenuItems = async function (app, objectGuid) { plugin.menu.items = (plugin.menu.items || []).map(item => { - const resource = item.label; const requiredPermissions = item.permissions || []; const debugTrace = []; @@ -77,25 +76,25 @@ module.exports = startMenuItems = async function (app, objectGuid) { const scopeMatch = userPerm.scope === required.scope; + const resourceMatch = + !userPerm.resource || + userPerm.resource === 'ALL' || + userPerm.resource === required.resource; + const actionMatch = userPerm.action === 'ALL' || userPerm.action === required.action || required.action === 'ALL'; - const resourceMatch = - !userPerm.resource || - userPerm.resource === 'ALL' || - userPerm.resource === resource; - - const result = scopeMatch && actionMatch && resourceMatch; + const result = scopeMatch && resourceMatch && actionMatch; if (debug) { debugTrace.push({ userPerm, required, scopeMatch, - actionMatch, resourceMatch, + actionMatch, result }); } @@ -105,7 +104,7 @@ module.exports = startMenuItems = async function (app, objectGuid) { }); if (debug) { - log(`\nMENU ITEM: ${item.label} - AUTHORIZED:', ${authorized} - TRACE:', ${debugTrace}`); + log(`\nMENU ITEM: ${item.label} - AUTHORIZED:', ${authorized} - TRACE:', ${JSON.stringify(debugTrace)}`); } return {