Add Active Directory group management functionality

Replit-Commit-Author: Agent
Replit-Commit-Session-Id: 705f2157-ef97-4fbd-89e4-8c7f2ecaea90
Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/7ed01c5f-a82d-405a-b728-b2e3d127c60c/6f419947-5d2b-4028-a9f5-2456de5a48d3.jpg
This commit is contained in:
alphaeusmote
2025-04-09 03:41:16 +00:00
parent 39080cea83
commit 00250d2904
5 changed files with 545 additions and 0 deletions
+215
View File
@@ -68,6 +68,7 @@
"input-otp": "^1.2.4",
"ioredis": "^5.6.0",
"jsonwebtoken": "^9.0.2",
"ldapjs": "^3.0.7",
"lucide-react": "^0.453.0",
"memorystore": "^1.6.7",
"morgan": "^1.10.0",
@@ -1514,6 +1515,101 @@
"integrity": "sha512-4JQNk+3mVzK3xh2rqd6RB4J46qUR19azEHBneZyTZM+c456qOrbbM/5xcR8huNCCcbVt7+UmizG6GuUvPvKUYg==",
"license": "MIT"
},
"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",
"license": "MIT"
},
"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",
"license": "MIT",
"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",
"license": "MIT",
"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",
"license": "MIT",
"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",
"license": "MIT"
},
"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",
"license": "MIT",
"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",
"license": "MIT",
"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",
"license": "MIT",
"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",
"license": "MIT"
},
"node_modules/@neondatabase/serverless": {
"version": "0.10.4",
"resolved": "https://registry.npmjs.org/@neondatabase/serverless/-/serverless-0.10.4.tgz",
@@ -3786,6 +3882,12 @@
"vite": "^4.2.0 || ^5.0.0"
}
},
"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==",
"license": "MIT"
},
"node_modules/accepts": {
"version": "1.3.8",
"resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz",
@@ -3872,6 +3974,15 @@
"integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==",
"license": "MIT"
},
"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==",
"license": "MIT",
"engines": {
"node": ">=0.8"
}
},
"node_modules/autoprefixer": {
"version": "10.4.20",
"resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.20.tgz",
@@ -3910,6 +4021,18 @@
"postcss": "^8.1.0"
}
},
"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==",
"license": "MIT",
"dependencies": {
"precond": "0.2"
},
"engines": {
"node": ">= 0.6"
}
},
"node_modules/balanced-match": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
@@ -4369,6 +4492,12 @@
"integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==",
"license": "MIT"
},
"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==",
"license": "MIT"
},
"node_modules/cross-spawn": {
"version": "7.0.6",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
@@ -5546,6 +5675,15 @@
"integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
"license": "MIT"
},
"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"
],
"license": "MIT"
},
"node_modules/fast-equals": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/fast-equals/-/fast-equals-5.0.1.tgz",
@@ -6204,6 +6342,29 @@
"safe-buffer": "^5.0.1"
}
},
"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",
"license": "MIT",
"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_modules/lilconfig": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.1.0.tgz",
@@ -7217,6 +7378,20 @@
"integrity": "sha512-i/hbxIE9803Alj/6ytL7UHQxRvZkI9O4Sy+J3HGc4F4oo/2eQAjTSNJ0bfxyse3bH0nuVesCk+3IRLaMtG3H6w==",
"license": "MIT"
},
"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-warning": {
"version": "2.3.2",
"resolved": "https://registry.npmjs.org/process-warning/-/process-warning-2.3.2.tgz",
"integrity": "sha512-n9wh8tvBe5sFmsqlg+XQhaQLumwpqoAUruLwjCopgTmUBjJ/fjtBsJzKleCaIGBOMXYEhp1YfKl4d7rJ5ZKJGA==",
"license": "MIT"
},
"node_modules/prop-types": {
"version": "15.8.1",
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
@@ -8953,6 +9128,32 @@
"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"
],
"license": "MIT",
"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"
],
"license": "MIT",
"dependencies": {
"assert-plus": "^1.0.0",
"core-util-is": "1.0.2",
"extsprintf": "^1.2.0"
}
},
"node_modules/vaul": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/vaul/-/vaul-1.1.1.tgz",
@@ -8966,6 +9167,20 @@
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0"
}
},
"node_modules/verror": {
"version": "1.10.1",
"resolved": "https://registry.npmjs.org/verror/-/verror-1.10.1.tgz",
"integrity": "sha512-veufcmxri4e3XSrT0xwfUR7kguIkaxBeosDg00yDWhk49wdwkSUrvvsm7nc75e1PUyvIeZj6nS8VQRYz2/S4Xg==",
"license": "MIT",
"dependencies": {
"assert-plus": "^1.0.0",
"core-util-is": "1.0.2",
"extsprintf": "^1.2.0"
},
"engines": {
"node": ">=0.6.0"
}
},
"node_modules/victory-vendor": {
"version": "36.9.2",
"resolved": "https://registry.npmjs.org/victory-vendor/-/victory-vendor-36.9.2.tgz",
+1
View File
@@ -70,6 +70,7 @@
"input-otp": "^1.2.4",
"ioredis": "^5.6.0",
"jsonwebtoken": "^9.0.2",
"ldapjs": "^3.0.7",
"lucide-react": "^0.453.0",
"memorystore": "^1.6.7",
"morgan": "^1.10.0",
+5
View File
@@ -0,0 +1,5 @@
{pkgs}: {
deps = [
pkgs.postgresql
];
}
+323
View File
@@ -19,6 +19,7 @@ import {
requireAdmin,
initializeRBAC
} from "./authorization";
import { ldapClient } from "./ldap";
// Extend Express Request to include user property
interface Request extends ExpressRequest {
@@ -1003,6 +1004,148 @@ export async function registerRoutes(app: Express): Promise<Server> {
next(error);
}
});
/**
* @swagger
* /api/connections/{connectionId}/ad-groups:
* post:
* summary: Create a new Active Directory group
* tags: [AD Groups]
* security:
* - cookieAuth: []
* - bearerAuth: []
* parameters:
* - in: path
* name: connectionId
* required: true
* schema:
* type: integer
* description: LDAP connection ID
* requestBody:
* required: true
* content:
* application/json:
* schema:
* type: object
* required:
* - name
* - parentDN
* properties:
* name:
* type: string
* description: The name of the group (will be used for cn and sAMAccountName)
* parentDN:
* type: string
* description: The distinguished name of the parent container (OU or domain)
* description:
* type: string
* description: Group description
* groupType:
* type: string
* enum: ['Global', 'DomainLocal', 'Universal']
* default: 'Global'
* description: Group scope type
* groupCategory:
* type: string
* enum: ['Security', 'Distribution']
* default: 'Security'
* description: Group category type
* responses:
* 201:
* description: Group created successfully
* 400:
* description: Invalid input
* 401:
* description: Unauthorized
* 403:
* description: Forbidden
* 500:
* description: Server error
*/
app.post("/api/connections/:connectionId/ad-groups", authenticateApiToken, requirePermission(PERMISSIONS.CREATE_AD_GROUPS), async (req: Request, res, next) => {
try {
const connectionId = parseInt(req.params.connectionId);
const { name, parentDN, description, groupType = 'Global', groupCategory = 'Security' } = req.body;
// Basic validation
if (!name || !parentDN) {
return res.status(400).json({ message: "Group name and parent DN are required" });
}
// Get connection
const connection = await storage.getLdapConnection(connectionId);
if (!connection) {
return res.status(404).json({ message: "LDAP connection not found" });
}
// Create the DN for the new group
const groupDN = `CN=${name},${parentDN}`;
// Set the group type value based on type and category
const groupTypeValue =
(groupCategory === 'Security' ? 0x80000000 : 0) |
(groupType === 'Global' ? 0x2 : groupType === 'Universal' ? 0x8 : 0x4);
// Attributes for the new group
const groupAttributes = {
objectClass: ['top', 'group'],
cn: name,
sAMAccountName: name,
description: description || '',
groupType: groupTypeValue.toString()
};
// Create the group in AD
await ldapClient.createEntry(connectionId, groupDN, groupAttributes);
// Search for the newly created group to get its attributes
const searchResults = await ldapClient.searchGroups(connectionId, `(&(objectClass=group)(cn=${name}))`, [
'objectGUID', 'distinguishedName', 'canonicalName', 'cn', 'sAMAccountName', 'description', 'groupType'
]);
if (!searchResults || searchResults.length === 0) {
return res.status(500).json({ message: "Group created but could not retrieve details" });
}
const adGroup = searchResults[0];
// Store in database
const storedGroup = await storage.createAdGroup({
connectionId,
objectGUID: adGroup.objectGUID,
distinguishedName: adGroup.distinguishedName,
canonicalName: adGroup.canonicalName,
cn: adGroup.cn,
sAMAccountName: adGroup.sAMAccountName,
groupType: groupCategory + ' ' + groupType,
description: adGroup.description,
members: [], // No members initially
adProperties: adGroup // Store all attributes
});
// Add audit log
await storage.createAuditLogEntry({
userId: req.user.id,
connectionId,
action: 'create',
targetId: storedGroup.id.toString(),
details: {
objectType: 'group',
objectName: name,
distinguishedName: groupDN,
groupType: groupCategory + ' ' + groupType
}
});
res.status(201).json(storedGroup);
} catch (err) {
console.error("Error creating AD group:", err);
if (err.message && err.message.includes('Entry Already Exists')) {
return res.status(409).json({ message: "Group already exists" });
}
next(err);
}
});
/**
* @swagger
@@ -1353,6 +1496,186 @@ export async function registerRoutes(app: Express): Promise<Server> {
next(error);
}
});
/**
* @swagger
* /api/connections/{connectionId}/ad-computers:
* post:
* summary: Create a new Active Directory computer
* tags: [AD Computers]
* security:
* - cookieAuth: []
* - bearerAuth: []
* parameters:
* - in: path
* name: connectionId
* required: true
* schema:
* type: integer
* description: LDAP connection ID
* requestBody:
* required: true
* content:
* application/json:
* schema:
* type: object
* required:
* - name
* - parentDN
* properties:
* name:
* type: string
* description: The name of the computer (will be used for cn and sAMAccountName)
* parentDN:
* type: string
* description: The distinguished name of the parent container (OU or domain)
* description:
* type: string
* description: Computer description
* dnsHostName:
* type: string
* description: DNS host name of the computer
* operatingSystem:
* type: string
* description: Operating system of the computer
* operatingSystemVersion:
* type: string
* description: Operating system version
* enabled:
* type: boolean
* default: true
* description: Whether the computer account is enabled
* responses:
* 201:
* description: Computer created successfully
* 400:
* description: Invalid input
* 401:
* description: Unauthorized
* 403:
* description: Forbidden
* 500:
* description: Server error
*/
app.post("/api/connections/:connectionId/ad-computers", authenticateApiToken, requirePermission(PERMISSIONS.CREATE_AD_COMPUTERS), async (req: Request, res, next) => {
try {
const connectionId = parseInt(req.params.connectionId);
const {
name,
parentDN,
description,
dnsHostName,
operatingSystem,
operatingSystemVersion,
enabled = true
} = req.body;
// Basic validation
if (!name || !parentDN) {
return res.status(400).json({ message: "Computer name and parent DN are required" });
}
// Get connection
const connection = await storage.getLdapConnection(connectionId);
if (!connection) {
return res.status(404).json({ message: "LDAP connection not found" });
}
// Create the DN for the new computer
const computerDN = `CN=${name},${parentDN}`;
// The sAMAccountName needs to end with $
const sAMAccountName = name.endsWith('$') ? name : `${name}$`;
// Create the userAccountControl value
// 4096 = WORKSTATION_TRUST_ACCOUNT
// 2 = ACCOUNTDISABLE (if not enabled)
const userAccountControl = enabled ? 4096 : 4098;
// Set the dnsHostName if not provided
const actualDnsHostName = dnsHostName || `${name}.${connection.domain}`;
// Attributes for the new computer
const computerAttributes: {
objectClass: string[];
cn: string;
sAMAccountName: string;
userAccountControl: string;
description: string;
dNSHostName: string;
operatingSystem?: string;
operatingSystemVersion?: string;
} = {
objectClass: ['top', 'computer'],
cn: name,
sAMAccountName: sAMAccountName,
userAccountControl: userAccountControl.toString(),
description: description || '',
dNSHostName: actualDnsHostName
};
// Add operating system information if provided
if (operatingSystem) {
computerAttributes.operatingSystem = operatingSystem;
}
if (operatingSystemVersion) {
computerAttributes.operatingSystemVersion = operatingSystemVersion;
}
// Create the computer in AD
await ldapClient.createEntry(connectionId, computerDN, computerAttributes);
// Search for the newly created computer to get its attributes
const searchResults = await ldapClient.searchComputers(connectionId, `(&(objectClass=computer)(cn=${name}))`, [
'objectGUID', 'distinguishedName', 'canonicalName', 'cn', 'sAMAccountName', 'description',
'dNSHostName', 'operatingSystem', 'operatingSystemVersion', 'userAccountControl'
]);
if (!searchResults || searchResults.length === 0) {
return res.status(500).json({ message: "Computer created but could not retrieve details" });
}
const adComputer = searchResults[0];
// Store in database
const storedComputer = await storage.createAdComputer({
connectionId,
objectGUID: adComputer.objectGUID,
distinguishedName: adComputer.distinguishedName,
canonicalName: adComputer.canonicalName,
cn: adComputer.cn,
name: adComputer.cn,
sAMAccountName: adComputer.sAMAccountName,
dnsHostName: adComputer.dNSHostName,
operatingSystem: adComputer.operatingSystem,
operatingSystemVersion: adComputer.operatingSystemVersion,
enabled: (parseInt(adComputer.userAccountControl) & 2) === 0, // Check if ACCOUNTDISABLE flag is not set
adProperties: adComputer // Store all attributes
});
// Add audit log
await storage.createAuditLogEntry({
userId: req.user.id,
connectionId,
action: 'create',
targetId: storedComputer.id.toString(),
details: {
distinguishedName: computerDN,
enabled: enabled,
name: name
}
});
res.status(201).json(storedComputer);
} catch (err) {
console.error("Error creating AD computer:", err);
if (err.message && err.message.includes('Entry Already Exists')) {
return res.status(409).json({ message: "Computer already exists" });
}
next(err);
}
});
/**
* @swagger
+1
View File
@@ -198,6 +198,7 @@ export const adComputers = pgTable("ad_computers", {
canonicalName: text("canonical_name"),
cn: text("cn"),
name: text("name").notNull(),
sAMAccountName: text("sam_account_name"),
dnsHostName: text("dns_host_name"),
operatingSystem: text("operating_system"),
operatingSystemVersion: text("operating_system_version"),