diff --git a/dbcreate.sql b/dbcreate.sql
index 292fc96..c684320 100644
--- a/dbcreate.sql
+++ b/dbcreate.sql
@@ -552,51 +552,16 @@ GO
CREATE OR ALTER VIEW dbo.vPermissionOverview AS
-SELECT
- p.ID AS Permission_ID,
- p.Scope,
- p.Resource,
- p.Action,
- p.Scope + '.' + p.Resource + '.' + p.Action AS PermissionKey,
-
- r.ID AS Role_ID,
-
-
- -- 🔥 NEU: Anzahl Rollen pro Permission
- COUNT(r.ID) OVER (PARTITION BY p.ID) AS RoleCount,
-
- COUNT(gr.Group_ObjectGUID) AS GroupCount,
- COUNT(ar.Authentication_ObjectGUID) AS DirectUserCount,
- COUNT(ag.Authentication_ObjectGUID) AS GroupUserCount,
-
- COUNT(
- COALESCE(ar.Authentication_ObjectGUID, ag.Authentication_ObjectGUID)
- ) AS TotalUserCount
-
-FROM dbo.Permission AS p
-
-INNER JOIN dbo.RolePermissions AS rp
- ON rp.Permission_ID = p.ID
-
-INNER JOIN dbo.Role AS r
- ON r.ID = rp.Role_ID
-
-LEFT JOIN dbo.GroupRoles AS gr
- ON gr.Role_ID = r.ID
-
-LEFT JOIN dbo.AuthenticationRoles AS ar
- ON ar.Role_ID = r.ID
-
-LEFT JOIN dbo.AuthenticationGroups AS ag
- ON ag.Group_ObjectGUID = gr.Group_ObjectGUID
-
-GROUP BY
- p.ID,
- p.Scope,
- p.Resource,
- p.Action,
- r.ID,
- r.Name;
+SELECT p.ID AS Permission_ID, p.Scope, p.Resource, p.Action, p.Scope + '.' + p.Resource + '.' + p.Action AS PermissionKey, r.ID AS Role_ID, /* 🔥 NEU: Anzahl Rollen pro Permission*/ COUNT(r.ID) OVER (PARTITION BY p.ID)
+ AS RoleCount, COUNT(gr.Group_ObjectGUID) AS GroupCount, COUNT(ar.Authentication_ObjectGUID) AS DirectUserCount, COUNT(ag.Authentication_ObjectGUID) AS GroupUserCount,
+ COUNT(COALESCE (ar.Authentication_ObjectGUID, ag.Authentication_ObjectGUID)) AS TotalUserCount
+FROM dbo.Permission AS p FULL JOIN
+ dbo.RolePermissions AS rp ON rp.Permission_ID = p.ID LEFT JOIN
+ dbo.Role AS r ON r.ID = rp.Role_ID LEFT JOIN
+ dbo.GroupRoles AS gr ON gr.Role_ID = r.ID LEFT JOIN
+ dbo.AuthenticationRoles AS ar ON ar.Role_ID = r.ID LEFT JOIN
+ dbo.AuthenticationGroups AS ag ON ag.Group_ObjectGUID = gr.Group_ObjectGUID
+GROUP BY p.ID, p.Scope, p.Resource, p.Action, r.ID, r.Name;
GO
diff --git a/globalize.js b/globalize.js
index 6fb8995..3bae33b 100644
--- a/globalize.js
+++ b/globalize.js
@@ -14,6 +14,7 @@ module.exports = {
localPath,
cache: {
startMenuItems: [],
+ clientsOnline: []
},
runtimeFile: {
package: new HotReload(path.join(localPath.root, 'package.json')),
diff --git a/package-lock.json b/package-lock.json
index 5e4f950..a02b8bd 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -9,7 +9,6 @@
"version": "0.9",
"license": "UNLICENSED",
"dependencies": {
- "activedirectory2": "^2.2.0",
"bcryptjs": "^3.0.2",
"body-parser": "^2.2.0",
"child_process": "^1.0.2",
@@ -21,7 +20,7 @@
"fs-extra": "^11.3.2",
"https": "^1.0.0",
"jsonwebtoken": "^9.0.2",
- "ldapjs": "^3.0.7",
+ "ldapts": "^8.1.7",
"module-alias": "^2.2.3",
"multer": "^2.0.2",
"net": "^1.0.2",
@@ -298,92 +297,6 @@
"resolved": "https://registry.npmjs.org/@js-joda/core/-/core-5.6.5.tgz",
"integrity": "sha512-3zwefSMwHpu8iVUW8YYz227sIv6UFqO31p1Bf1ZH/Vom7CmNyUsXjDBlnNzcuhmOL1XfxZ3nvND42kR23XlbcQ=="
},
- "node_modules/@ldapjs/asn1": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/@ldapjs/asn1/-/asn1-2.0.0.tgz",
- "integrity": "sha512-G9+DkEOirNgdPmD0I8nu57ygQJKOOgFEMKknEuQvIHbGLwP3ny1mY+OTUYLCbCaGJP4sox5eYgBJRuSUpnAddA==",
- "deprecated": "This package has been decomissioned. See https://github.com/ldapjs/node-ldapjs/blob/8ffd0bc9c149088a10ec4c1ec6a18450f76ad05d/README.md"
- },
- "node_modules/@ldapjs/attribute": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/@ldapjs/attribute/-/attribute-1.0.0.tgz",
- "integrity": "sha512-ptMl2d/5xJ0q+RgmnqOi3Zgwk/TMJYG7dYMC0Keko+yZU6n+oFM59MjQOUht5pxJeS4FWrImhu/LebX24vJNRQ==",
- "deprecated": "This package has been decomissioned. See https://github.com/ldapjs/node-ldapjs/blob/8ffd0bc9c149088a10ec4c1ec6a18450f76ad05d/README.md",
- "dependencies": {
- "@ldapjs/asn1": "2.0.0",
- "@ldapjs/protocol": "^1.2.1",
- "process-warning": "^2.1.0"
- }
- },
- "node_modules/@ldapjs/change": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/@ldapjs/change/-/change-1.0.0.tgz",
- "integrity": "sha512-EOQNFH1RIku3M1s0OAJOzGfAohuFYXFY4s73wOhRm4KFGhmQQ7MChOh2YtYu9Kwgvuq1B0xKciXVzHCGkB5V+Q==",
- "deprecated": "This package has been decomissioned. See https://github.com/ldapjs/node-ldapjs/blob/8ffd0bc9c149088a10ec4c1ec6a18450f76ad05d/README.md",
- "dependencies": {
- "@ldapjs/asn1": "2.0.0",
- "@ldapjs/attribute": "1.0.0"
- }
- },
- "node_modules/@ldapjs/controls": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/@ldapjs/controls/-/controls-2.1.0.tgz",
- "integrity": "sha512-2pFdD1yRC9V9hXfAWvCCO2RRWK9OdIEcJIos/9cCVP9O4k72BY1bLDQQ4KpUoJnl4y/JoD4iFgM+YWT3IfITWw==",
- "deprecated": "This package has been decomissioned. See https://github.com/ldapjs/node-ldapjs/blob/8ffd0bc9c149088a10ec4c1ec6a18450f76ad05d/README.md",
- "dependencies": {
- "@ldapjs/asn1": "^1.2.0",
- "@ldapjs/protocol": "^1.2.1"
- }
- },
- "node_modules/@ldapjs/controls/node_modules/@ldapjs/asn1": {
- "version": "1.2.0",
- "resolved": "https://registry.npmjs.org/@ldapjs/asn1/-/asn1-1.2.0.tgz",
- "integrity": "sha512-KX/qQJ2xxzvO2/WOvr1UdQ+8P5dVvuOLk/C9b1bIkXxZss8BaR28njXdPgFCpj5aHaf1t8PmuVnea+N9YG9YMw==",
- "deprecated": "This package has been decomissioned. See https://github.com/ldapjs/node-ldapjs/blob/8ffd0bc9c149088a10ec4c1ec6a18450f76ad05d/README.md"
- },
- "node_modules/@ldapjs/dn": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/@ldapjs/dn/-/dn-1.1.0.tgz",
- "integrity": "sha512-R72zH5ZeBj/Fujf/yBu78YzpJjJXG46YHFo5E4W1EqfNpo1UsVPqdLrRMXeKIsJT3x9dJVIfR6OpzgINlKpi0A==",
- "deprecated": "This package has been decomissioned. See https://github.com/ldapjs/node-ldapjs/blob/8ffd0bc9c149088a10ec4c1ec6a18450f76ad05d/README.md",
- "dependencies": {
- "@ldapjs/asn1": "2.0.0",
- "process-warning": "^2.1.0"
- }
- },
- "node_modules/@ldapjs/filter": {
- "version": "2.1.1",
- "resolved": "https://registry.npmjs.org/@ldapjs/filter/-/filter-2.1.1.tgz",
- "integrity": "sha512-TwPK5eEgNdUO1ABPBUQabcZ+h9heDORE4V9WNZqCtYLKc06+6+UAJ3IAbr0L0bYTnkkWC/JEQD2F+zAFsuikNw==",
- "deprecated": "This package has been decomissioned. See https://github.com/ldapjs/node-ldapjs/blob/8ffd0bc9c149088a10ec4c1ec6a18450f76ad05d/README.md",
- "dependencies": {
- "@ldapjs/asn1": "2.0.0",
- "@ldapjs/protocol": "^1.2.1",
- "process-warning": "^2.1.0"
- }
- },
- "node_modules/@ldapjs/messages": {
- "version": "1.3.0",
- "resolved": "https://registry.npmjs.org/@ldapjs/messages/-/messages-1.3.0.tgz",
- "integrity": "sha512-K7xZpXJ21bj92jS35wtRbdcNrwmxAtPwy4myeh9duy/eR3xQKvikVycbdWVzkYEAVE5Ce520VXNOwCHjomjCZw==",
- "deprecated": "This package has been decomissioned. See https://github.com/ldapjs/node-ldapjs/blob/8ffd0bc9c149088a10ec4c1ec6a18450f76ad05d/README.md",
- "dependencies": {
- "@ldapjs/asn1": "^2.0.0",
- "@ldapjs/attribute": "^1.0.0",
- "@ldapjs/change": "^1.0.0",
- "@ldapjs/controls": "^2.1.0",
- "@ldapjs/dn": "^1.1.0",
- "@ldapjs/filter": "^2.1.1",
- "@ldapjs/protocol": "^1.2.1",
- "process-warning": "^2.2.0"
- }
- },
- "node_modules/@ldapjs/protocol": {
- "version": "1.2.1",
- "resolved": "https://registry.npmjs.org/@ldapjs/protocol/-/protocol-1.2.1.tgz",
- "integrity": "sha512-O89xFDLW2gBoZWNXuXpBSM32/KealKCTb3JGtJdtUQc7RjAk8XzrRgyz02cPAwGKwKPxy0ivuC7UP9bmN87egQ==",
- "deprecated": "This package has been decomissioned. See https://github.com/ldapjs/node-ldapjs/blob/8ffd0bc9c149088a10ec4c1ec6a18450f76ad05d/README.md"
- },
"node_modules/@socket.io/component-emitter": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.2.tgz",
@@ -455,11 +368,6 @@
"node": ">=6.5"
}
},
- "node_modules/abstract-logging": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/abstract-logging/-/abstract-logging-2.0.1.tgz",
- "integrity": "sha512-2BjRTZxTPvheOvGbBslFSYOUkr+SjPtOnrLP33f+VIWLzezQpZcqVg7ja3L4dBXmzzgwT+a029jRx5PCi3JuiA=="
- },
"node_modules/accepts": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz",
@@ -472,40 +380,6 @@
"node": ">= 0.6"
}
},
- "node_modules/activedirectory2": {
- "version": "2.2.0",
- "resolved": "https://registry.npmjs.org/activedirectory2/-/activedirectory2-2.2.0.tgz",
- "integrity": "sha512-uGbw74xttFG6hgocU8T1a0oDofLsyTp44BPTn42JN5C2QlyO5kRl2E7ZoUdfpFzV+yxhaQTKI+8QqRB5HONYvA==",
- "deprecated": "Decomissioned.",
- "dependencies": {
- "abstract-logging": "^2.0.0",
- "async": "^3.1.0",
- "ldapjs": "^2.3.3",
- "merge-options": "^2.0.0"
- },
- "engines": {
- "node": ">=4.0"
- }
- },
- "node_modules/activedirectory2/node_modules/ldapjs": {
- "version": "2.3.3",
- "resolved": "https://registry.npmjs.org/ldapjs/-/ldapjs-2.3.3.tgz",
- "integrity": "sha512-75QiiLJV/PQqtpH+HGls44dXweviFwQ6SiIK27EqzKQ5jU/7UFrl2E5nLdQ3IYRBzJ/AVFJI66u0MZ0uofKYwg==",
- "deprecated": "This package has been decomissioned. See https://github.com/ldapjs/node-ldapjs/blob/8ffd0bc9c149088a10ec4c1ec6a18450f76ad05d/README.md",
- "dependencies": {
- "abstract-logging": "^2.0.0",
- "asn1": "^0.2.4",
- "assert-plus": "^1.0.0",
- "backoff": "^2.5.0",
- "ldap-filter": "^0.3.3",
- "once": "^1.4.0",
- "vasync": "^2.2.0",
- "verror": "^1.8.1"
- },
- "engines": {
- "node": ">=10.13.0"
- }
- },
"node_modules/agent-base": {
"version": "7.1.4",
"resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz",
@@ -541,38 +415,6 @@
"resolved": "https://registry.npmjs.org/append-field/-/append-field-1.0.0.tgz",
"integrity": "sha512-klpgFSWLW1ZEs8svjfb7g4qWY0YS5imI82dTg+QahUvJ8YqAY0P10Uk8tTyh9ZGuYEZEMaeJYCF5BFuX552hsw=="
},
- "node_modules/asn1": {
- "version": "0.2.6",
- "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.6.tgz",
- "integrity": "sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ==",
- "dependencies": {
- "safer-buffer": "~2.1.0"
- }
- },
- "node_modules/assert-plus": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz",
- "integrity": "sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw==",
- "engines": {
- "node": ">=0.8"
- }
- },
- "node_modules/async": {
- "version": "3.2.6",
- "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz",
- "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA=="
- },
- "node_modules/backoff": {
- "version": "2.5.0",
- "resolved": "https://registry.npmjs.org/backoff/-/backoff-2.5.0.tgz",
- "integrity": "sha512-wC5ihrnUXmR2douXmXLCe5O3zg3GKIyvRi/hi58a/XyRxVI+3/yM0PYueQOZXPXQ9pxBislYkw+sF9b7C/RuMA==",
- "dependencies": {
- "precond": "0.2"
- },
- "engines": {
- "node": ">= 0.6"
- }
- },
"node_modules/balanced-match": {
"version": "4.0.4",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz",
@@ -885,11 +727,6 @@
"node": ">=6.6.0"
}
},
- "node_modules/core-util-is": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz",
- "integrity": "sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ=="
- },
"node_modules/cors": {
"version": "2.8.5",
"resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz",
@@ -1219,14 +1056,6 @@
"node": ">=22.15.0"
}
},
- "node_modules/extsprintf": {
- "version": "1.4.1",
- "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.4.1.tgz",
- "integrity": "sha512-Wrk35e8ydCKDj/ArClo1VrPVmN8zph5V4AtHwIuHhvMXsKf73UT3BOD+azBIW+3wOJ4FhEH7zyaJCFvChjYvMA==",
- "engines": [
- "node >=0.6.0"
- ]
- },
"node_modules/finalhandler": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.0.tgz",
@@ -1558,14 +1387,6 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
- "node_modules/is-plain-obj": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz",
- "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==",
- "engines": {
- "node": ">=8"
- }
- },
"node_modules/is-promise": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz",
@@ -1660,37 +1481,15 @@
"safe-buffer": "^5.0.1"
}
},
- "node_modules/ldap-filter": {
- "version": "0.3.3",
- "resolved": "https://registry.npmjs.org/ldap-filter/-/ldap-filter-0.3.3.tgz",
- "integrity": "sha512-/tFkx5WIn4HuO+6w9lsfxq4FN3O+fDZeO9Mek8dCD8rTUpqzRa766BOBO7BcGkn3X86m5+cBm1/2S/Shzz7gMg==",
+ "node_modules/ldapts": {
+ "version": "8.1.7",
+ "resolved": "https://registry.npmjs.org/ldapts/-/ldapts-8.1.7.tgz",
+ "integrity": "sha512-TJl6T92eIwMf/OJ0hDfKVa6ISwzo+lqCWCI5Mf//ARlKa3LKQZaSrme/H2rCRBhW0DZCQlrsV+fgoW5YHRNLUw==",
"dependencies": {
- "assert-plus": "^1.0.0"
+ "strict-event-emitter-types": "2.0.0"
},
"engines": {
- "node": ">=0.8"
- }
- },
- "node_modules/ldapjs": {
- "version": "3.0.7",
- "resolved": "https://registry.npmjs.org/ldapjs/-/ldapjs-3.0.7.tgz",
- "integrity": "sha512-1ky+WrN+4CFMuoekUOv7Y1037XWdjKpu0xAPwSP+9KdvmV9PG+qOKlssDV6a+U32apwxdD3is/BZcWOYzN30cg==",
- "deprecated": "This package has been decomissioned. See https://github.com/ldapjs/node-ldapjs/blob/8ffd0bc9c149088a10ec4c1ec6a18450f76ad05d/README.md",
- "dependencies": {
- "@ldapjs/asn1": "^2.0.0",
- "@ldapjs/attribute": "^1.0.0",
- "@ldapjs/change": "^1.0.0",
- "@ldapjs/controls": "^2.1.0",
- "@ldapjs/dn": "^1.1.0",
- "@ldapjs/filter": "^2.1.1",
- "@ldapjs/messages": "^1.3.0",
- "@ldapjs/protocol": "^1.2.1",
- "abstract-logging": "^2.0.1",
- "assert-plus": "^1.0.0",
- "backoff": "^2.5.0",
- "once": "^1.4.0",
- "vasync": "^2.2.1",
- "verror": "^1.10.1"
+ "node": ">=20"
}
},
"node_modules/lodash": {
@@ -1769,17 +1568,6 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
- "node_modules/merge-options": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/merge-options/-/merge-options-2.0.0.tgz",
- "integrity": "sha512-S7xYIeWHl2ZUKF7SDeBhGg6rfv5bKxVBdk95s/I7wVF8d+hjLSztJ/B271cnUiF6CAFduEQ5Zn3HYwAjT16DlQ==",
- "dependencies": {
- "is-plain-obj": "^2.0.0"
- },
- "engines": {
- "node": ">=8"
- }
- },
"node_modules/mime-db": {
"version": "1.54.0",
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz",
@@ -3854,14 +3642,6 @@
"resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.9.1.tgz",
"integrity": "sha512-nkc6NpDcvPVpZXxrreI/FOtX3XemeLl8E0qFr6F2Lrm/I8WOnaWNhIPK2Z7OHpw7gh5XJThi6j6ppgNoaT1w4w=="
},
- "node_modules/precond": {
- "version": "0.2.3",
- "resolved": "https://registry.npmjs.org/precond/-/precond-0.2.3.tgz",
- "integrity": "sha512-QCYG84SgGyGzqJ/vlMsxeXd/pgL/I94ixdNFyh1PusWmTCyVfPJjZ1K1jvHtsbfnXQs2TSkEP2fR7QiMZAnKFQ==",
- "engines": {
- "node": ">= 0.6"
- }
- },
"node_modules/process": {
"version": "0.11.10",
"resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz",
@@ -3870,11 +3650,6 @@
"node": ">= 0.6.0"
}
},
- "node_modules/process-warning": {
- "version": "2.3.2",
- "resolved": "https://registry.npmjs.org/process-warning/-/process-warning-2.3.2.tgz",
- "integrity": "sha512-n9wh8tvBe5sFmsqlg+XQhaQLumwpqoAUruLwjCopgTmUBjJ/fjtBsJzKleCaIGBOMXYEhp1YfKl4d7rJ5ZKJGA=="
- },
"node_modules/proxy-addr": {
"version": "2.0.7",
"resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz",
@@ -4402,6 +4177,11 @@
"node": ">=10.0.0"
}
},
+ "node_modules/strict-event-emitter-types": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/strict-event-emitter-types/-/strict-event-emitter-types-2.0.0.tgz",
+ "integrity": "sha512-Nk/brWYpD85WlOgzw5h173aci0Teyv8YdIAEtV+N88nDB0dLlazZyJMIsN6eo1/AR61l+p6CJTG1JIyFaoNEEA=="
+ },
"node_modules/string_decoder": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
@@ -4629,43 +4409,6 @@
"node": ">= 0.8"
}
},
- "node_modules/vasync": {
- "version": "2.2.1",
- "resolved": "https://registry.npmjs.org/vasync/-/vasync-2.2.1.tgz",
- "integrity": "sha512-Hq72JaTpcTFdWiNA4Y22Amej2GH3BFmBaKPPlDZ4/oC8HNn2ISHLkFrJU4Ds8R3jcUi7oo5Y9jcMHKjES+N9wQ==",
- "engines": [
- "node >=0.6.0"
- ],
- "dependencies": {
- "verror": "1.10.0"
- }
- },
- "node_modules/vasync/node_modules/verror": {
- "version": "1.10.0",
- "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz",
- "integrity": "sha512-ZZKSmDAEFOijERBLkmYfJ+vmk3w+7hOLYDNkRCuRuMJGEmqYNCNLyBBFwWKVMhfwaEF3WOd0Zlw86U/WC/+nYw==",
- "engines": [
- "node >=0.6.0"
- ],
- "dependencies": {
- "assert-plus": "^1.0.0",
- "core-util-is": "1.0.2",
- "extsprintf": "^1.2.0"
- }
- },
- "node_modules/verror": {
- "version": "1.10.1",
- "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.1.tgz",
- "integrity": "sha512-veufcmxri4e3XSrT0xwfUR7kguIkaxBeosDg00yDWhk49wdwkSUrvvsm7nc75e1PUyvIeZj6nS8VQRYz2/S4Xg==",
- "dependencies": {
- "assert-plus": "^1.0.0",
- "core-util-is": "1.0.2",
- "extsprintf": "^1.2.0"
- },
- "engines": {
- "node": ">=0.6.0"
- }
- },
"node_modules/which": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
diff --git a/package.json b/package.json
index e86c12e..40963c2 100644
--- a/package.json
+++ b/package.json
@@ -21,7 +21,6 @@
"license": "UNLICENSED",
"licensefile": "license_internal.txt",
"dependencies": {
- "activedirectory2": "^2.2.0",
"bcryptjs": "^3.0.2",
"body-parser": "^2.2.0",
"child_process": "^1.0.2",
@@ -33,7 +32,7 @@
"fs-extra": "^11.3.2",
"https": "^1.0.0",
"jsonwebtoken": "^9.0.2",
- "ldapjs": "^3.0.7",
+ "ldapts": "^8.1.7",
"module-alias": "^2.2.3",
"multer": "^2.0.2",
"net": "^1.0.2",
diff --git a/public/javascript/rbacAPI.js b/public/javascript/rbacAPI.js
index 4d315fe..394026f 100644
--- a/public/javascript/rbacAPI.js
+++ b/public/javascript/rbacAPI.js
@@ -30,11 +30,13 @@ const RBAC = {
}))
.sort((a, b) => a.sAMAccountName.localeCompare(b.sAMAccountName)),
createUser: (data) => api('/api/rbac/auth/create', 'POST', data),
+ syncUsersFromAD: () => api('/api/rbac/auth/syncFromAD', 'POST', { }),
deleteUser: (guid) => api(`/api/rbac/auth/${guid}`, 'DELETE'),
// 👥 GROUPS
loadGroups: () => api('/api/rbac/group/get', 'POST'),
createGroup: (name) => api('/api/rbac/group/create', 'POST', { name }),
+ syncGroupsFromAD: () => api('/api/rbac/group/syncFromAD', 'POST', { }),
deleteGroup: (guid) => api(`/api/rbac/group/${guid}`, 'DELETE'),
// 🎭 ROLES
@@ -193,7 +195,7 @@ const rbacPermissionsVT = virtualTable({
data: [],
rowHeight: 20,
buffer: 5,
- groupKey: 'Scope',
+ groupKey: null,
rowKey: 'ID',
filterConfig: {
hideCounter: true,
@@ -372,6 +374,13 @@ async function createUser() {
loadUsers();
}
+
+async function syncUsersFromAD() {
+ const users = await RBAC.syncUsersFromAD();
+ sendUserEvent('RBAC', `${users.length} Benutzer aus dem AD synchronisiert`, null, 0);
+ loadUsers();
+}
+
async function deleteUser(guid, name) {
feedbox({
title: `Benutzer löschen`,
@@ -420,6 +429,16 @@ async function deleteGroup(guid, name) {
}
+async function syncGroupsFromAD() {
+ const group = await RBAC.syncGroupsFromAD();
+
+ sendUserEvent('RBAC', `${group.length} Gruppen aus dem AD synchronisiert`, null, 0);
+
+ loadGroups();
+ loadUsers();
+}
+
+
//////////////////////////////
// 🎭 ROLE ACTIONS
//////////////////////////////
diff --git a/public/styles/default.css b/public/styles/default.css
index f3a42ce..960f85f 100644
--- a/public/styles/default.css
+++ b/public/styles/default.css
@@ -43,7 +43,7 @@ button:not(:disabled).yellowbutton:hover { background:var(--theme-button-yellow-
.card.static.row { overflow:hidden; display:flex; flex-direction:row; flex-wrap: wrap;}
.card.static { overflow:hidden; display:flex; flex-direction:column; }
-.container { width:calc(100% - 20px); margin:10px auto; display:grid; grid-template-columns:100%; gap:12px; min-height:0; overflow:auto; max-height:100%; }
+.container:not(.static) { width:calc(100% - 20px); margin:10px auto; display:grid; grid-template-columns:100%; gap:12px; min-height:0; overflow:auto; max-height:100%; }
.container:not(.static) * { box-sizing:border-box; }
.card { border-width:1px; border-style:solid; border-radius:8px; padding:20px; }
diff --git a/public/views/authentications.hbs b/public/views/authentications.hbs
deleted file mode 100644
index e69de29..0000000
diff --git a/public/views/desktop.hbs b/public/views/desktop.hbs
index 869d6ea..bf56ab6 100644
--- a/public/views/desktop.hbs
+++ b/public/views/desktop.hbs
@@ -149,4 +149,24 @@
}
});
+
+/* Checks tab visibility
+document.addEventListener(
+ 'visibilitychange',
+ () => {
+
+ if (document.hidden) {
+
+ alert('AWAY AT: ' + new Date().toISOString());
+
+ } else {
+
+ updateActivity();
+ }
+ }
+);
+*/
+
+
+
diff --git a/public/views/rbac.hbs b/public/views/rbac.hbs
index 9c5f6ea..5e96b85 100644
--- a/public/views/rbac.hbs
+++ b/public/views/rbac.hbs
@@ -29,7 +29,7 @@ section {
align-items: center;
justify-content: space-between;
padding: 5px;
- width: 120px;
+ width: 200px;
border: 1px solid #ccc;
border-radius: 8px;
margin: 0 2px 2px 0;
@@ -62,12 +62,21 @@ input {
-
-
-
- Users
-
-
+
+
+
+
+
+ AD Synchronisation:
+
+
+
+
+
+ Users
+
+
+
|
@@ -88,8 +97,30 @@ input {
+
+
+
+
+
+
+
+ GRUPPEN WERDEN GELADEN . . .
+
+
+
+
+
+
+
+
+
+ ROLLEN WERDEN GELADEN . . .
+
+
+
+
-
-
-
-
-
-
- GRUPPEN WERDEN GELADEN . . .
-
-
-
-
-
-
-
-
-
- ROLLEN WERDEN GELADEN . . .
-
-
diff --git a/server.js b/server.js
index 71e7ed0..59d5e1e 100644
--- a/server.js
+++ b/server.js
@@ -57,7 +57,8 @@ const server = https.createServer(httpsOptions, app);
const io = new Server(server, {
- pingTimeout: 60000,
+ pingInterval: 1000, // alle 25s Ping senden
+ pingTimeout: 200, // 20s ohne Pong => disconnect
maxHttpBufferSize: 1e8, // 100 MB
});
diff --git a/src/models/authenticationGroupsModel.js b/src/models/authenticationGroupsModel.js
index 8fc6792..de7ae02 100644
--- a/src/models/authenticationGroupsModel.js
+++ b/src/models/authenticationGroupsModel.js
@@ -12,6 +12,15 @@ module.exports = (sequelize) => {
}
}, {
tableName: 'AuthenticationGroups',
+ indexes: [
+ {
+ unique: true,
+ fields: [
+ 'Authentication_ObjectGUID',
+ 'Group_ObjectGUID'
+ ]
+ }
+ ],
timestamps: false
});
diff --git a/src/routes/adminRoutes.js b/src/routes/adminRoutes.js
index d312e1c..c331f4f 100644
--- a/src/routes/adminRoutes.js
+++ b/src/routes/adminRoutes.js
@@ -102,7 +102,6 @@ module.exports = {
app.post('/api/rbac/auth/get', async (req, res) => {
try {
- console.log(await service.get('rbacManager').syncAuthByActiveDirectory());
rbacUsers = await service.get('rbacManager').getAuth();
res.json(rbacUsers);
} catch (err) {
@@ -138,6 +137,17 @@ module.exports = {
}
});
+ app.post('/api/rbac/auth/syncFromAD', async (req, res) => {
+ try {
+ rbacUsers = await service.get('rbacManager').syncAuthByActiveDirectory();
+ console.log(await service.get('rbacManager').syncAuthenticationGroupsFromAD());
+ res.json(rbacUsers);
+ } catch (err) {
+ service.get('eventManager').writeLog(req.cookies.ObjectGUID, 4, 'RBAC', err.message);
+ res.status(500).json({ error: err.message });
+ }
+ });
+
app.put('/api/rbac/auth/:id', async (req, res) => {
try {
await service.get('rbacManager').updateAuth(req.params.id, req.body);
@@ -189,6 +199,17 @@ module.exports = {
res.status(500).json({ error: err.message });
}
});
+
+ app.post('/api/rbac/group/syncFromAD', async (req, res) => {
+ try {
+ console.log(await service.get('rbacManager').syncGroupClosureFromAD());
+ rbacGroups = await service.get('rbacManager').syncGroupByActiveDirectory();
+ res.json(rbacGroups);
+ } catch (err) {
+ service.get('eventManager').writeLog(req.cookies.ObjectGUID, 4, 'RBAC', err);
+ res.status(500).json({ error: err.message });
+ }
+ });
app.put('/api/rbac/group/:id', async (req, res) => {
try {
diff --git a/src/services/activeDirectoryManager.js b/src/services/activeDirectoryManager.js
index 06760da..dc4a8fa 100644
--- a/src/services/activeDirectoryManager.js
+++ b/src/services/activeDirectoryManager.js
@@ -1,26 +1,21 @@
-const ActiveDirectory = require('activedirectory2');
+const { Client } = require('ldapts');
class ActiveDirectoryManager {
- constructor({
- url,
- baseDN,
- username,
+
+ constructor({
+ url,
+ baseDN,
+ username,
password,
- userAttributes,
- groupAttributes,
- computerAttributes
+ userAttributes = [],
+ groupAttributes = [],
+ computerAttributes = []
}) {
- this.ad = new ActiveDirectory({
- url,
- baseDN,
- username,
- password,
- attributes: {
- user: userAttributes,
- group: groupAttributes,
- computer: computerAttributes
- }
- });
+
+ this.url = url;
+ this.baseDN = baseDN;
+ this.username = username;
+ this.password = password;
this.userAttributes = userAttributes;
this.groupAttributes = groupAttributes;
@@ -29,250 +24,556 @@ class ActiveDirectoryManager {
/**
* -----------------------------------------------------
- * INTERNAL GENERIC LDAP SEARCH
+ * CONNECTION
* -----------------------------------------------------
*/
- async ldapSearch(options) {
- return new Promise((resolve, reject) => {
- this.ad.find(options, (err, result) => {
- if (err) return reject(err);
- resolve(result || {});
- });
+
+ async createClient() {
+
+ const client = new Client({
+ url: this.url,
+ timeout: 10000,
+ connectTimeout: 10000
});
+
+ await client.bind(this.username, this.password);
+
+ return client;
}
/**
* -----------------------------------------------------
- * USER FUNCTIONS
+ * HELPERS
+ * -----------------------------------------------------
+ */
+
+ escape(value) {
+ return String(value).replace(/[*()\\]/g, '\\$&');
+ }
+
+ bufferToGuid(buffer) {
+
+ const hex = buffer.toString('hex');
+
+ return [
+ hex.substring(6, 8),
+ hex.substring(4, 6),
+ hex.substring(2, 4),
+ hex.substring(0, 2),
+ '-',
+ hex.substring(10, 12),
+ hex.substring(8, 10),
+ '-',
+ hex.substring(14, 16),
+ hex.substring(12, 14),
+ '-',
+ hex.substring(16, 20),
+ '-',
+ hex.substring(20)
+ ].join('');
+ }
+
+ normalizeEntry(entry) {
+
+ const obj = {};
+
+ for (const [key, value] of Object.entries(entry)) {
+
+ let normalized = value;
+
+ // GUID konvertieren
+ if (key === 'objectGUID' && value instanceof Buffer) {
+ normalized = this.bufferToGuid(value);
+ }
+
+ // Arrays mit einem Wert -> String
+ else if (Array.isArray(value)) {
+
+ if (value.length === 0) {
+ normalized = null;
+ }
+
+ else if (value.length === 1) {
+ normalized = value[0];
+ }
+ }
+
+ obj[key] = normalized;
+ }
+
+ obj.ObjectSource_ID = 2;
+
+ return obj;
+ }
+
+ /**
+ * -----------------------------------------------------
+ * GENERIC LDAP SEARCH
+ * -----------------------------------------------------
+ */
+
+ async ldapSearch({
+ baseDN = this.baseDN,
+ filter,
+ attributes = []
+ }) {
+
+ const client = await this.createClient();
+
+ try {
+
+ const { searchEntries } = await client.search(baseDN, {
+ scope: 'sub',
+ filter,
+ attributes,
+ paged: true
+ });
+
+ return searchEntries.map(entry =>
+ this.normalizeEntry(entry)
+ );
+
+ } finally {
+ await client.unbind();
+ }
+ }
+
+ /**
+ * -----------------------------------------------------
+ * USERS
* -----------------------------------------------------
*/
async getUser(username, attributes = this.userAttributes) {
- return new Promise((resolve, reject) => {
- this.ad.findUser({ attributes }, username, (err, user) => {
- if (err) return reject(err);
- resolve(user || null);
- });
+
+ const safe = this.escape(username);
+
+ const filter =
+ `(&(objectCategory=person)(objectClass=user)` +
+ `(|(sAMAccountName=${safe})` +
+ `(mail=${safe})` +
+ `(cn=${safe})))`;
+
+ const users = await this.ldapSearch({
+ filter,
+ attributes
+ });
+
+ return users[0] || null;
+ }
+
+ async getUserDN(username) {
+
+ const user = await this.getUser(
+ username,
+ ['distinguishedName']
+ );
+
+ return user?.distinguishedName || null;
+ }
+
+ async getAllUsers(attributes = this.userAttributes) {
+
+ const filter =
+ '(&(objectCategory=person)(objectClass=user))';
+
+ return await this.ldapSearch({
+ filter,
+ attributes
});
}
-
- async getAllUsers(attributes = this.userAttributes) {
- const options = {
- baseDN: this.ad.baseDN,
- filter: '(&(objectClass=user)(objectCategory=person))',
- attributes: ['objectGUID'
- ,'sAMAccountName'
- ,'mail'
- ,'givenName'
- ,'sn'
- ,'employeeID'
- ,'title'
- ,'department'
- ,'streetAddress'
- ,'telephoneNumber'
- ,'physicalDeliveryOfficeName'
- ,'distinguishedName']
- };
- const result = await this.ldapSearch(options);
- console.log(result)
- return result.users || [];
- }
-
-
- async getUserDN(username) {
- const user = await this.getUser(username);
- return user?.dn || null;
- }
-
async findUsers(query, attributes = this.userAttributes) {
- return new Promise((resolve, reject) => {
- const filter = `(&(objectClass=user)(|(cn=${query})(sAMAccountName=${query})(mail=${query})(displayName=${query})))`;
- this.ad.findUsers({ filter, attributes }, (err, users) => {
- if (err) return reject(err);
- resolve(users || []);
- });
+ const safe = this.escape(query);
+
+ const filter =
+ `(&(objectClass=user)` +
+ `(|(cn=${safe})` +
+ `(sAMAccountName=${safe})` +
+ `(mail=${safe})` +
+ `(displayName=${safe})))`;
+
+ return await this.ldapSearch({
+ filter,
+ attributes
});
}
/**
* -----------------------------------------------------
- * GROUP FUNCTIONS
+ * GROUPS
* -----------------------------------------------------
*/
async getGroup(groupName, attributes = this.groupAttributes) {
- return new Promise((resolve, reject) => {
- this.ad.findGroup({ attributes }, groupName, (err, group) => {
- if (err) return reject(err);
- resolve(group || null);
- });
- });
- }
- async findGroups(query, attributes = this.groupAttributes) {
- return new Promise((resolve, reject) => {
- const filter = `(&(objectClass=group)(cn=${query}))`;
+ const safe = this.escape(groupName);
- this.ad.findGroups({ filter, attributes }, (err, groups) => {
- if (err) return reject(err);
- resolve(groups || []);
- });
- });
- }
+ const filter =
+ `(&(objectClass=group)(cn=${safe}))`;
- /**
- * -----------------------------------------------------
- * COMPUTER / OU FUNCTIONS 🖥️
- * -----------------------------------------------------
- */
-
- /**
- * Einzelnen Computer holen
- */
- async getComputer(name, attributes = this.computerAttributes) {
- return new Promise((resolve, reject) => {
- const filter = `(&(objectClass=computer)(|(cn=${name})(dNSHostName=${name})))`;
-
- this.ad.find({ filter, attributes }, (err, result) => {
- if (err) return reject(err);
- resolve(result?.other?.[0] || null);
- });
- });
- }
-
-
- /**
- * Alle Computer
- */
- async getComputers(attributes = this.computerAttributes) {
- const options = {
- baseDN: this.ad.baseDN,
- filter: '(objectClass=computer)',
- attributes
- };
-
- const result = await this.ldapSearch(options);
- return result.other || [];
- }
-
-
- /**
- * Alle Computer aus einer OU holen
- */
- async getComputersFromOU(ouDn, attributes = this.computerAttributes) {
- const options = {
- baseDN: ouDn,
- filter: '(objectClass=computer)',
- attributes
- };
-
- const result = await this.ldapSearch(options);
- return result.other || [];
- }
-
- /**
- * Computer suchen (Wildcard möglich)
- * Beispiele: "PC-*", "*LAPTOP*", "SRV01"
- */
- async findComputers(query, attributes = this.computerAttributes) {
- const filter = `(&(objectClass=computer)(|(cn=${query})(dNSHostName=${query})))`;
-
- const result = await this.ldapSearch({
+ const groups = await this.ldapSearch({
filter,
attributes
});
- return result.other || [];
+
+ return groups[0] || null;
}
- /**
- * -----------------------------------------------------
- * GROUP MEMBERSHIP (DIRECT & RECURSIVE)
- * -----------------------------------------------------
- */
+ async findGroups(query, attributes = this.groupAttributes) {
- async isUserMemberOfDirect(username, groupName) {
- return new Promise((resolve, reject) => {
- this.ad.isUserMemberOf(username, groupName, (err, isMember) => {
- if (err) return reject(err);
- resolve(isMember);
- });
+ const safe = this.escape(query);
+
+ const filter =
+ `(&(objectClass=group)(cn=${safe}))`;
+
+ return await this.ldapSearch({
+ filter,
+ attributes
});
}
- async isUserMemberOfRecursive(username, groupName, visited = new Set()) {
- const key = groupName.toLowerCase();
- if (visited.has(key)) return false;
- visited.add(key);
+ async getAllGroups({
+ attributes = this.groupAttributes,
+ includeMembers = false,
+ memberAttributes = this.userAttributes || [
+ 'objectGUID',
+ 'cn',
+ 'sAMAccountName',
+ 'mail',
+ 'distinguishedName'
+ ]
+ } = {}) {
- const direct = await this.isUserMemberOfDirect(username, groupName);
- if (direct) return true;
+ const finalAttributes = [...new Set([
+ ...attributes,
+ ...(includeMembers ? ['member'] : [])
+ ])];
- const group = await this.getGroup(groupName);
- if (!group || !Array.isArray(group.member)) return false;
+ const filter = '(objectClass=group)';
- for (const dn of group.member) {
- const match = dn.match(/CN=([^,]+)/i);
- if (!match) continue;
-
- const subGroupName = match[1];
- const found = await this.isUserMemberOfRecursive(username, subGroupName, visited);
- if (found) return true;
- }
-
- return false;
- }
-
- async getGroupSubgroups(groupName, visited = new Set()) {
- const key = groupName.toLowerCase();
- if (visited.has(key)) return [];
-
- visited.add(key);
-
- const group = await this.getGroup(groupName);
- if (!group || !Array.isArray(group.member)) return [];
+ const groups = await this.ldapSearch({
+ filter,
+ attributes: finalAttributes
+ });
const results = [];
- for (const memberDN of group.member) {
- const match = memberDN.match(/CN=([^,]+)/i);
- if (!match) continue;
+ for (const group of groups) {
- const subGroupName = match[1];
- const sub = await this.getGroup(subGroupName).catch(() => null);
- if (!sub) continue;
+ const result = {};
- results.push(sub);
- results.push(...await this.getGroupSubgroups(subGroupName, visited));
+ // Gruppenattribute übernehmen
+ for (const attr of attributes) {
+ result[attr] = group[attr] ?? null;
+ }
+
+ // Mitglieder laden
+ if (includeMembers) {
+
+ const rawMembers = Array.isArray(group.member)
+ ? group.member
+ : group.member
+ ? [group.member]
+ : [];
+
+ const members = [];
+
+ for (const memberDN of rawMembers) {
+
+ const memberResults = await this.ldapSearch({
+ baseDN: memberDN,
+ filter: '(objectClass=*)',
+ attributes: memberAttributes
+ });
+
+ if (memberResults.length > 0) {
+
+ const member = memberResults[0];
+
+ const normalizedMember = {};
+
+ for (const attr of memberAttributes) {
+ normalizedMember[attr] = member[attr] ?? null;
+ }
+
+ members.push(normalizedMember);
+ }
+ }
+
+ result.members = members;
+ }
+
+ results.push(result);
}
return results;
}
- async getGroupRecursive(groupName, visited = new Set()) {
+ /**
+ * -----------------------------------------------------
+ * COMPUTERS
+ * -----------------------------------------------------
+ */
+
+ async getComputer(name, attributes = this.computerAttributes) {
+
+ const safe = this.escape(name);
+
+ const filter =
+ `(&(objectClass=computer)` +
+ `(|(cn=${safe})(dNSHostName=${safe})))`;
+
+ const results = await this.ldapSearch({
+ filter,
+ attributes
+ });
+
+ return results[0] || null;
+ }
+
+ async getComputers(attributes = this.computerAttributes) {
+
+ return await this.ldapSearch({
+ filter: '(objectClass=computer)',
+ attributes
+ });
+ }
+
+ async getComputersFromOU(
+ ouDn,
+ attributes = this.computerAttributes
+ ) {
+
+ return await this.ldapSearch({
+ baseDN: ouDn,
+ filter: '(objectClass=computer)',
+ attributes
+ });
+ }
+
+ async findComputers(
+ query,
+ attributes = this.computerAttributes
+ ) {
+
+ const safe = this.escape(query);
+
+ const filter =
+ `(&(objectClass=computer)` +
+ `(|(cn=${safe})(dNSHostName=${safe})))`;
+
+ return await this.ldapSearch({
+ filter,
+ attributes
+ });
+ }
+
+ /**
+ * -----------------------------------------------------
+ * GROUP MEMBERSHIP
+ * -----------------------------------------------------
+ */
+
+ async isUserMemberOfDirect(username, groupName) {
+
+ const user = await this.getUser(
+ username,
+ ['distinguishedName']
+ );
+
+ if (!user) {
+ return false;
+ }
+
+ const safeGroup = this.escape(groupName);
+
+ const filter =
+ `(&(objectClass=group)` +
+ `(cn=${safeGroup})` +
+ `(member=${user.distinguishedName}))`;
+
+ const results = await this.ldapSearch({
+ filter,
+ attributes: ['cn']
+ });
+
+ return results.length > 0;
+ }
+
+ async isUserMemberOfRecursive(
+ username,
+ groupName,
+ visited = new Set()
+ ) {
+
const key = groupName.toLowerCase();
- if (visited.has(key)) return null;
+
+ if (visited.has(key)) {
+ return false;
+ }
+
+ visited.add(key);
+
+ const direct =
+ await this.isUserMemberOfDirect(
+ username,
+ groupName
+ );
+
+ if (direct) {
+ return true;
+ }
+
+ const group = await this.getGroup(
+ groupName,
+ ['member']
+ );
+
+ if (!group || !group.member) {
+ return false;
+ }
+
+ const members = Array.isArray(group.member)
+ ? group.member
+ : [group.member];
+
+ for (const dn of members) {
+
+ const match = dn.match(/CN=([^,]+)/i);
+
+ if (!match) {
+ continue;
+ }
+
+ const subGroupName = match[1];
+
+ const found =
+ await this.isUserMemberOfRecursive(
+ username,
+ subGroupName,
+ visited
+ );
+
+ if (found) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ async getGroupSubgroups(
+ groupName,
+ visited = new Set()
+ ) {
+
+ const key = groupName.toLowerCase();
+
+ if (visited.has(key)) {
+ return [];
+ }
+
+ visited.add(key);
+
+ const group = await this.getGroup(
+ groupName,
+ ['member']
+ );
+
+ if (!group || !group.member) {
+ return [];
+ }
+
+ const members = Array.isArray(group.member)
+ ? group.member
+ : [group.member];
+
+ const results = [];
+
+ for (const dn of members) {
+
+ const match = dn.match(/CN=([^,]+)/i);
+
+ if (!match) {
+ continue;
+ }
+
+ const subGroupName = match[1];
+
+ const subGroup = await this.getGroup(
+ subGroupName
+ ).catch(() => null);
+
+ if (!subGroup) {
+ continue;
+ }
+
+ results.push(subGroup);
+
+ results.push(
+ ...await this.getGroupSubgroups(
+ subGroupName,
+ visited
+ )
+ );
+ }
+
+ return results;
+ }
+
+ async getGroupRecursive(
+ groupName,
+ visited = new Set()
+ ) {
+
+ const key = groupName.toLowerCase();
+
+ if (visited.has(key)) {
+ return null;
+ }
visited.add(key);
const group = await this.getGroup(groupName);
- if (!group) return null;
+
+ if (!group) {
+ return null;
+ }
const result = {
...group,
subgroups: []
};
- if (!Array.isArray(group.member)) return result;
+ if (!group.member) {
+ return result;
+ }
- for (const memberDN of group.member) {
- const match = memberDN.match(/CN=([^,]+)/i);
- if (!match) continue;
+ const members = Array.isArray(group.member)
+ ? group.member
+ : [group.member];
+
+ for (const dn of members) {
+
+ const match = dn.match(/CN=([^,]+)/i);
+
+ if (!match) {
+ continue;
+ }
const subGroupName = match[1];
- const subTree = await this.getGroupRecursive(subGroupName, visited);
- if (subTree) result.subgroups.push(subTree);
+
+ const subTree =
+ await this.getGroupRecursive(
+ subGroupName,
+ visited
+ );
+
+ if (subTree) {
+ result.subgroups.push(subTree);
+ }
}
return result;
@@ -281,13 +582,10 @@ class ActiveDirectoryManager {
module.exports = ActiveDirectoryManager;
-
-
-
-
-// const ldap = require('ldapjs');
+// const { Client } = require('ldapts');
// class ActiveDirectoryManager {
+
// constructor({
// url,
// baseDN,
@@ -297,6 +595,7 @@ module.exports = ActiveDirectoryManager;
// groupAttributes = [],
// computerAttributes = []
// }) {
+
// this.url = url;
// this.baseDN = baseDN;
// this.username = username;
@@ -305,50 +604,30 @@ module.exports = ActiveDirectoryManager;
// this.userAttributes = userAttributes;
// this.groupAttributes = groupAttributes;
// this.computerAttributes = computerAttributes;
+// }
-// this.client = ldap.createClient({
+// /**
+// * -----------------------------------------------------
+// * CONNECTION
+// * -----------------------------------------------------
+// */
+
+// async createClient() {
+
+// const client = new Client({
// url: this.url,
-// reconnect: true,
// timeout: 10000,
// connectTimeout: 10000
// });
+
+// await client.bind(this.username, this.password);
+
+// return client;
// }
// /**
// * -----------------------------------------------------
-// * CONNECTION HANDLING
-// * -----------------------------------------------------
-// */
-// async bind() {
-// return new Promise((resolve, reject) => {
-// this.client.bind(this.username, this.password, (err) => {
-// if (err) return reject(err);
-// resolve();
-// });
-// });
-// }
-
-// async unbind() {
-// return new Promise((resolve, reject) => {
-// this.client.unbind(err => {
-// if (err) return reject(err);
-// resolve();
-// });
-// });
-// }
-
-// async withConnection(fn) {
-// try {
-// await this.bind();
-// return await fn();
-// } finally {
-// await this.unbind();
-// }
-// }
-
-// /**
-// * -----------------------------------------------------
-// * INTERNAL HELPERS
+// * HELPERS
// * -----------------------------------------------------
// */
@@ -356,130 +635,276 @@ module.exports = ActiveDirectoryManager;
// return String(value).replace(/[*()\\]/g, '\\$&');
// }
-// async ldapSearch({ baseDN = this.baseDN, filter, attributes = [] }) {
-// const opts = {
-// filter,
-// scope: 'sub',
-// attributes,
-// paged: true
-// };
+// bufferToGuid(buffer) {
-// return new Promise((resolve, reject) => {
-// const results = [];
+// const hex = buffer.toString('hex');
-// this.client.search(baseDN, opts, (err, res) => {
-// if (err) return reject(err);
+// return [
+// hex.substring(6, 8),
+// hex.substring(4, 6),
+// hex.substring(2, 4),
+// hex.substring(0, 2),
+// '-',
+// hex.substring(10, 12),
+// hex.substring(8, 10),
+// '-',
+// hex.substring(14, 16),
+// hex.substring(12, 14),
+// '-',
+// hex.substring(16, 20),
+// '-',
+// hex.substring(20)
+// ].join('');
+// }
-// res.on('searchEntry', (entry) => {
-// results.push(entry.object);
-// });
+// normalizeEntry(entry) {
-// res.on('error', (err) => reject(err));
-// res.on('end', () => resolve(results));
+// const obj = {};
+
+// for (const [key, value] of Object.entries(entry)) {
+
+// let normalized = value;
+
+// // GUID konvertieren
+// if (key === 'objectGUID' && value instanceof Buffer) {
+// normalized = this.bufferToGuid(value);
+// }
+
+// // Arrays mit einem Wert -> String
+// else if (Array.isArray(value)) {
+
+// if (value.length === 0) {
+// normalized = null;
+// }
+
+// else if (value.length === 1) {
+// normalized = value[0];
+// }
+// }
+
+// obj[key] = normalized;
+// }
+// obj['ObjectSource_ID'] = 2;
+// return obj;
+// }
+
+// /**
+// * -----------------------------------------------------
+// * GENERIC SEARCH
+// * -----------------------------------------------------
+// */
+
+// async ldapSearch({
+// baseDN = this.baseDN,
+// filter,
+// attributes = []
+// }) {
+
+// const client = await this.createClient();
+
+// try {
+
+// const { searchEntries } = await client.search(baseDN, {
+// scope: 'sub',
+// filter,
+// attributes,
+// paged: true
// });
-// });
+
+// return searchEntries.map(entry =>
+// this.normalizeEntry(entry)
+// );
+
+// } finally {
+// await client.unbind();
+// }
// }
// /**
// * -----------------------------------------------------
-// * USER FUNCTIONS
+// * USERS
// * -----------------------------------------------------
// */
// async getUser(username, attributes = this.userAttributes) {
+
// const safe = this.escape(username);
-// const filter = `(&(objectCategory=person)(objectClass=user)(|(sAMAccountName=${safe})(mail=${safe})(cn=${safe}))))`;
+// const filter =
+// `(&(objectCategory=person)(objectClass=user)` +
+// `(|(sAMAccountName=${safe})(mail=${safe})(cn=${safe})))`;
-// const res = await this.ldapSearch({ filter, attributes });
-// return res[0] || null;
+// const users = await this.ldapSearch({
+// filter,
+// attributes
+// });
+
+// return users[0] || null;
// }
-// async getUserDN(username) {
-// const user = await this.getUser(username);
-// return user?.distinguishedName || null;
-// }
+// async getAllUsers(attributes = [
+// 'objectGUID',
+// 'sAMAccountName',
+// 'mail',
+// 'givenName',
+// 'sn',
+// 'employeeID',
+// 'title',
+// 'department',
+// 'streetAddress',
+// 'telephoneNumber',
+// 'physicalDeliveryOfficeName',
+// 'distinguishedName'
+// ]) {
-// async findUsers(query, attributes = this.userAttributes) {
-// const safe = this.escape(query);
-
-// const filter = `(&(objectCategory=person)(objectClass=user)(|(cn=${safe})(sAMAccountName=${safe})(mail=${safe})(displayName=${safe})))`;
-
-// return await this.ldapSearch({ filter, attributes });
-// }
-
-// async getAllUsers(attributes = this.userAttributes) {
-// const filter = '(&(objectCategory=person)(objectClass=user))';
-
-// return await this.ldapSearch({ filter, attributes });
-// }
-
-// /**
-// * -----------------------------------------------------
-// * GROUP FUNCTIONS
-// * -----------------------------------------------------
-// */
-
-// async getGroup(groupName, attributes = this.groupAttributes) {
-// const safe = this.escape(groupName);
-
-// const filter = `(&(objectClass=group)(cn=${safe}))`;
-
-// const res = await this.ldapSearch({ filter, attributes });
-// return res[0] || null;
-// }
-
-// async findGroups(query, attributes = this.groupAttributes) {
-// const safe = this.escape(query);
-
-// const filter = `(&(objectClass=group)(cn=${safe}))`;
-
-// return await this.ldapSearch({ filter, attributes });
-// }
-
-// async getAllGroups(attributes = this.groupAttributes) {
-// const filter = '(objectClass=group)';
-
-// return await this.ldapSearch({ filter, attributes });
-// }
-
-// /**
-// * -----------------------------------------------------
-// * COMPUTER / OU FUNCTIONS
-// * -----------------------------------------------------
-// */
-
-// async getComputer(name, attributes = this.computerAttributes) {
-// const safe = this.escape(name);
-
-// const filter = `(&(objectClass=computer)(|(cn=${safe})(dNSHostName=${safe})))`;
-
-// const res = await this.ldapSearch({ filter, attributes });
-// return res[0] || null;
-// }
-
-// async getComputers(attributes = this.computerAttributes) {
-// const filter = '(objectClass=computer)';
-
-// return await this.ldapSearch({ filter, attributes });
-// }
-
-// async getComputersFromOU(ouDn, attributes = this.computerAttributes) {
-// const filter = '(objectClass=computer)';
+// const filter =
+// '(&(objectCategory=person)(objectClass=user))';
// return await this.ldapSearch({
-// baseDN: ouDn,
// filter,
// attributes
// });
// }
-// async findComputers(query, attributes = this.computerAttributes) {
+// async findUsers(query, attributes = this.userAttributes) {
+
// const safe = this.escape(query);
-// const filter = `(&(objectClass=computer)(|(cn=${safe})(dNSHostName=${safe})))`;
+// const filter =
+// `(&(objectClass=user)` +
+// `(|(cn=${safe})` +
+// `(sAMAccountName=${safe})` +
+// `(mail=${safe})` +
+// `(displayName=${safe})))`;
-// return await this.ldapSearch({ filter, attributes });
+// return await this.ldapSearch({
+// filter,
+// attributes
+// });
+// }
+
+// /**
+// * -----------------------------------------------------
+// * GROUPS
+// * -----------------------------------------------------
+// */
+
+// async getGroup(groupName, attributes = this.groupAttributes) {
+
+// const safe = this.escape(groupName);
+
+// const filter =
+// `(&(objectClass=group)(cn=${safe}))`;
+
+// const groups = await this.ldapSearch({
+// filter,
+// attributes
+// });
+
+// return groups[0] || null;
+// }
+
+// /**
+// * -----------------------------------------------------
+// * ALL GROUPS
+// * -----------------------------------------------------
+// */
+
+// /**
+// * -----------------------------------------------------
+// * ALL GROUPS
+// * -----------------------------------------------------
+// */
+
+// async getAllGroups({ attributes = this.groupAttributes, includeMembers = false } = {}) {
+
+// const finalAttributes = [...new Set([
+// ...attributes,
+// ...(includeMembers ? ['member'] : [])
+// ])];
+
+// const filter = '(objectClass=group)';
+
+// const groups = await this.ldapSearch({
+// filter,
+// attributes: finalAttributes
+// });
+
+// return groups.map(group => {
+
+// const result = {};
+
+// // Nur definierte Attribute übernehmen
+// for (const attr of attributes) {
+// result[attr] = group[attr] ?? null;
+// }
+// result['ObjectSource_ID'] = 2;
+
+// // Optional Mitglieder hinzufügen
+// if (includeMembers) {
+
+// const rawMembers = Array.isArray(group.member)
+// ? group.member
+// : group.member
+// ? [group.member]
+// : [];
+
+// result.members = rawMembers.map(memberDN => {
+
+// const match = memberDN.match(/CN=([^,]+)/i);
+
+// return {
+// distinguishedName: memberDN,
+// cn: match ? match[1] : null
+// };
+// });
+// }
+
+// return result;
+// });
+// }
+
+// async findGroups(query, attributes = this.groupAttributes) {
+
+// const safe = this.escape(query);
+
+// const filter =
+// `(&(objectClass=group)(cn=${safe}))`;
+
+// return await this.ldapSearch({
+// filter,
+// attributes
+// });
+// }
+
+// /**
+// * -----------------------------------------------------
+// * COMPUTERS
+// * -----------------------------------------------------
+// */
+
+// async getComputers(attributes = this.computerAttributes) {
+
+// return await this.ldapSearch({
+// filter: '(objectClass=computer)',
+// attributes
+// });
+// }
+
+// async getComputer(name, attributes = this.computerAttributes) {
+
+// const safe = this.escape(name);
+
+// const filter =
+// `(&(objectClass=computer)` +
+// `(|(cn=${safe})(dNSHostName=${safe})))`;
+
+// const results = await this.ldapSearch({
+// filter,
+// attributes
+// });
+
+// return results[0] || null;
// }
// /**
@@ -489,100 +914,29 @@ module.exports = ActiveDirectoryManager;
// */
// async isUserMemberOfDirect(username, groupName) {
-// const user = await this.getUser(username, ['distinguishedName']);
-// if (!user) return false;
-// const userDN = user.distinguishedName;
+// const user = await this.getUser(username, [
+// 'distinguishedName'
+// ]);
+
+// if (!user) {
+// return false;
+// }
+
// const safeGroup = this.escape(groupName);
-// const filter = `(&(objectClass=group)(cn=${safeGroup})(member=${userDN}))`;
+// const filter =
+// `(&(objectClass=group)` +
+// `(cn=${safeGroup})` +
+// `(member=${user.distinguishedName}))`;
-// const res = await this.ldapSearch({ filter, attributes: ['cn'] });
-// return res.length > 0;
-// }
+// const results = await this.ldapSearch({
+// filter,
+// attributes: ['cn']
+// });
-// async isUserMemberOfRecursive(username, groupName, visited = new Set()) {
-// const key = groupName.toLowerCase();
-// if (visited.has(key)) return false;
-// visited.add(key);
-
-// const direct = await this.isUserMemberOfDirect(username, groupName);
-// if (direct) return true;
-
-// const group = await this.getGroup(groupName, ['member']);
-// if (!group || !group.member) return false;
-
-// const members = Array.isArray(group.member) ? group.member : [group.member];
-
-// for (const dn of members) {
-// const match = dn.match(/CN=([^,]+)/i);
-// if (!match) continue;
-
-// const subGroup = match[1];
-// const found = await this.isUserMemberOfRecursive(username, subGroup, visited);
-// if (found) return true;
-// }
-
-// return false;
-// }
-
-// async getGroupSubgroups(groupName, visited = new Set()) {
-// const key = groupName.toLowerCase();
-// if (visited.has(key)) return [];
-
-// visited.add(key);
-
-// const group = await this.getGroup(groupName, ['member']);
-// if (!group || !group.member) return [];
-
-// const members = Array.isArray(group.member) ? group.member : [group.member];
-
-// const results = [];
-
-// for (const dn of members) {
-// const match = dn.match(/CN=([^,]+)/i);
-// if (!match) continue;
-
-// const subGroupName = match[1];
-// const sub = await this.getGroup(subGroupName).catch(() => null);
-// if (!sub) continue;
-
-// results.push(sub);
-// results.push(...await this.getGroupSubgroups(subGroupName, visited));
-// }
-
-// return results;
-// }
-
-// async getGroupRecursive(groupName, visited = new Set()) {
-// const key = groupName.toLowerCase();
-// if (visited.has(key)) return null;
-
-// visited.add(key);
-
-// const group = await this.getGroup(groupName);
-// if (!group) return null;
-
-// const result = {
-// ...group,
-// subgroups: []
-// };
-
-// if (!group.member) return result;
-
-// const members = Array.isArray(group.member) ? group.member : [group.member];
-
-// for (const dn of members) {
-// const match = dn.match(/CN=([^,]+)/i);
-// if (!match) continue;
-
-// const subGroupName = match[1];
-// const subTree = await this.getGroupRecursive(subGroupName, visited);
-// if (subTree) result.subgroups.push(subTree);
-// }
-
-// return result;
+// return results.length > 0;
// }
// }
-// module.exports = ActiveDirectoryManager;
+// module.exports = ActiveDirectoryManager;
\ No newline at end of file
diff --git a/src/services/authenticationManager.js b/src/services/authenticationManager.js
index 368907e..63837ce 100644
--- a/src/services/authenticationManager.js
+++ b/src/services/authenticationManager.js
@@ -57,6 +57,10 @@ class AuthenticationManager {
return { token: null, levelId: 2, message: 'Falsches Passwort' };
}
+ if(!user.active) {
+ return { token: null, levelId: 2, message: 'Benutzer nicht aktiv' };
+ }
+
const token = jwt.sign(
{
ObjectGUID: user.ObjectGUID,
@@ -67,12 +71,26 @@ class AuthenticationManager {
);
user.refreshtoken = token;
- user.online = true;
+ await this.setOnline(sAMAccountName);
await user.save();
return { token, levelId: 0, message: 'Erfolgreich angemeldet' };
}
+
+ async setOnline(sAMAccountName) {
+ const user = await this.findUser(sAMAccountName);
+ user.online = true;
+ await user.save();
+ }
+
+ async setOffline(sAMAccountName) {
+ const user = await this.findUser(sAMAccountName);
+ user.online = false;
+ await user.save();
+ }
+
+
// =========================================================
// LOGOUT
// =========================================================
@@ -85,8 +103,8 @@ class AuthenticationManager {
}
user.refreshtoken = null;
- user.online = false;
await user.save();
+ await this.setOffline();
return { token: null, levelId: 0, message: 'Erfolgreich abgemeldet' };
}
diff --git a/src/services/rbacManager.js b/src/services/rbacManager.js
index f9d1219..3efd05d 100644
--- a/src/services/rbacManager.js
+++ b/src/services/rbacManager.js
@@ -190,8 +190,9 @@ class RBACManager {
return res.redirect('/login');
}
- const user = await this.db.get('authentication').findOne( { where: { sAMAccountName } } );
+ req.user = {};
+ const user = await this.db.get('authentication').findOne( { where: { sAMAccountName } } );
if (!user || !user.active) {
return res.redirect('/login');
}
@@ -217,6 +218,7 @@ class RBACManager {
permissions: normalized,
isSuperAdmin
};
+
next();
} catch (err) {
@@ -254,19 +256,17 @@ async syncAuthByActiveDirectory() {
const all = await this.service.get('activeDirectoryManager').getAllUsers();
all.forEach(async user => {
- user.userAccountControl = user.userAccountControl_ID;
- user.distinguishedName = user.distinguishedName;
-
- await auth.upsert({
- objectGUID: user.ObjectGUID,
- ObjectSource_ID: 2,
- userAccountControl_ID: user.userAccountControl,
- mail: user.mail,
- displayName: user.displayName
- });
+ try {
+ if(user.objectGUID !== null) {
+ await auth.upsert({...user, ObjectGUID: user.objectGUID});
+ } else {
+ }
+ } catch(err) {
+ throw err;
+ }
})
- // return all;
+ return all;
}
@@ -319,6 +319,27 @@ async createGroup(data) {
});
}
+async syncGroupByActiveDirectory() {
+ const group = await this.db.get('group');
+ const all = await this.service.get('activeDirectoryManager').getAllGroups({
+ attributes: ['objectGUID', 'sAMAccountName', 'distinguishedName'],
+ includeMembers: false
+ });
+
+ all.forEach(async adGroup => {
+ try {
+ if(adGroup.objectGUID !== null) {
+ await group.upsert({...adGroup, ObjectGUID: adGroup.objectGUID, Name: adGroup.sAMAccountName});
+ } else {
+ }
+ } catch(err) {
+ throw err;
+ }
+ })
+
+ return all;
+}
+
async updateGroup(id, data) {
const Group = this.db.get('group');
@@ -367,6 +388,225 @@ async removeUserFromGroup(authId, groupId) {
});
}
+async syncAuthenticationGroupsFromAD() {
+
+ const AuthGroups = this.db.get('authenticationGroupsModel');
+
+ const users = await this.service
+ .get('activeDirectoryManager')
+ .getAllUsers([
+ 'objectGUID',
+ 'memberOf'
+ ]);
+
+ const groups = await this.service
+ .get('activeDirectoryManager')
+ .getAllGroups({
+ attributes: [
+ 'objectGUID',
+ 'distinguishedName'
+ ]
+ });
+
+ /**
+ * -----------------------------------------------------
+ * GROUP MAP (DN -> GUID)
+ * -----------------------------------------------------
+ */
+
+ const groupMap = new Map();
+
+ for (const g of groups) {
+
+ if (g.distinguishedName && g.objectGUID) {
+ groupMap.set(
+ g.distinguishedName.toLowerCase(),
+ g.objectGUID
+ );
+ }
+ }
+
+ /**
+ * -----------------------------------------------------
+ * BUILD RELATIONS
+ * -----------------------------------------------------
+ */
+
+ const relations = [];
+
+ for (const user of users) {
+
+ if (!user.objectGUID) continue;
+
+ const memberOf = Array.isArray(user.memberOf)
+ ? user.memberOf
+ : user.memberOf
+ ? [user.memberOf]
+ : [];
+
+ for (const dn of memberOf) {
+
+ const groupGuid = groupMap.get(dn.toLowerCase());
+
+ if (!groupGuid) continue;
+
+ relations.push({
+ Authentication_ObjectGUID: user.objectGUID,
+ Group_ObjectGUID: groupGuid
+ });
+ }
+ }
+
+ /**
+ * -----------------------------------------------------
+ * DB SYNC (clean rebuild)
+ * -----------------------------------------------------
+ */
+
+ // await AuthGroups.destroy({
+ // where: {}
+ // });
+
+ if (relations.length) {
+ await AuthGroups.upsert(relations, {
+ // ignoreDuplicates: true
+ });
+ }
+
+ return relations;
+}
+
+
+async syncGroupClosureFromAD() {
+
+ const GroupClosure = this.db.get('groupClosureModel');
+
+ const groups = await this.service
+ .get('activeDirectoryManager')
+ .getAllGroups({
+ attributes: [
+ 'objectGUID',
+ 'distinguishedName',
+ 'member'
+ ],
+ includeMembers: false
+ });
+
+ /**
+ * -----------------------------------------------------
+ * MAP: DN -> GUID
+ * -----------------------------------------------------
+ */
+
+ const groupMap = new Map();
+
+ for (const g of groups) {
+ if (g.distinguishedName && g.objectGUID) {
+ groupMap.set(
+ g.distinguishedName.toLowerCase(),
+ g.objectGUID
+ );
+ }
+ }
+
+ /**
+ * -----------------------------------------------------
+ * BUILD DIRECT RELATIONS
+ * -----------------------------------------------------
+ */
+
+ const edges = [];
+
+ for (const group of groups) {
+
+ if (!group.objectGUID) continue;
+
+ const members = Array.isArray(group.member)
+ ? group.member
+ : group.member
+ ? [group.member]
+ : [];
+
+ for (const memberDN of members) {
+
+ const childGUID = groupMap.get(
+ memberDN.toLowerCase()
+ );
+
+ if (!childGUID) continue;
+
+ edges.push({
+ ParentGroup_ObjectGUID: group.objectGUID,
+ ChildGroup_ObjectGUID: childGUID,
+ Depth: 1
+ });
+ }
+ }
+
+ /**
+ * -----------------------------------------------------
+ * TRANSITIVE CLOSURE (DFS)
+ * -----------------------------------------------------
+ */
+
+ const closure = [...edges];
+
+ const adjacency = new Map();
+
+ for (const e of edges) {
+
+ if (!adjacency.has(e.ParentGroup_ObjectGUID)) {
+ adjacency.set(e.ParentGroup_ObjectGUID, []);
+ }
+
+ adjacency.get(e.ParentGroup_ObjectGUID)
+ .push(e.ChildGroup_ObjectGUID);
+ }
+
+ const visitedCache = new Map();
+
+ const dfs = (root, current, depth) => {
+
+ const children = adjacency.get(current) || [];
+
+ for (const child of children) {
+
+ closure.push({
+ ParentGroup_ObjectGUID: root,
+ ChildGroup_ObjectGUID: child,
+ Depth: depth + 1
+ });
+
+ dfs(root, child, depth + 1);
+ }
+ };
+
+ for (const group of groups) {
+
+ const root = group.objectGUID;
+ if (!root) continue;
+
+ dfs(root, root, 0);
+ }
+
+ /**
+ * -----------------------------------------------------
+ * CLEAN & SAVE
+ * -----------------------------------------------------
+ */
+
+ // await GroupClosure.destroy({
+ // where: {}
+ // });
+
+ await GroupClosure.bulkCreate(closure, {
+ ignoreDuplicates: true
+ });
+
+ return closure.length;
+}
+
+
// =========================================================
// 🎭 ROLE CRUD
// =========================================================
diff --git a/src/services/socketManager.js b/src/services/socketManager.js
index 0612fc7..d9d54ad 100644
--- a/src/services/socketManager.js
+++ b/src/services/socketManager.js
@@ -25,11 +25,11 @@ class SocketManager {
userName: sAMAccountName
});
- // console.log(`${sAMAccountName} [${objectGuid}] connected to ${namespace}`);
+ console.log(`${sAMAccountName} [${objectGuid}] connected to ${namespace}`);
socket.on('disconnect', () => {
clients.delete(objectGuid);
- // console.log(`${sAMAccountName} [${objectGuid}] disconnected from ${namespace}`);
+ console.log(`${sAMAccountName} [${objectGuid}] disconnected from ${namespace}`);
});
});
diff --git a/src/sockets/mainSocket.js b/src/sockets/mainSocket.js
index bdbe2a3..96ca2c1 100644
--- a/src/sockets/mainSocket.js
+++ b/src/sockets/mainSocket.js
@@ -1,5 +1,7 @@
const path = require('path');
const fs = require('fs');
+const { cache } = require('@root/globalize.js');
+const { service } = require('@root/server.js');
module.exports = (app, socketManager, namespace, pluginManager, authenticationModel, fileSystemManager, eventManager, activeDirectory) => {
@@ -12,9 +14,26 @@ module.exports = (app, socketManager, namespace, pluginManager, authenticationMo
// eventManager.write(data.objectGuid, data.levelId, data.pluginName, data.message)
});
- // global.json.configuration.onChange(info => {
- // console.log(info.delta)
- // });
+
+
+ socketManager.io.on('connection', async (socket) => {
+ if(!socket.handshake.auth.objectGuid) {
+ return;
+ }
+ await service.get('authenticationManager').setOnline(socket.handshake.auth.sAMAccountName);
+ });
+
+ socketManager.on(namespace, 'disconnect', (socket, data) => {
+ if(!socket.handshake.auth.objectGuid) {
+ return;
+ }
+
+ setTimeout(async () => {
+ if(!socketManager.clients.get(namespace).has(socket.handshake.auth.objectGuid)) {
+ await service.get('authenticationManager').setOffline(socket.handshake.auth.sAMAccountName);
+ }
+ }, 8000);
+ });
mainSocket.on('connection', socket => {