From 4bdec2c296b8d799e2c0d53a6a712648e1391867 Mon Sep 17 00:00:00 2001 From: alphaeusmote <41258468-alphaeusmote@users.noreply.replit.com> Date: Fri, 11 Apr 2025 14:40:04 +0000 Subject: [PATCH] Add ability to update and delete Active Directory domains 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/10f17668-d1aa-4b91-b921-61237e6fe8f5.jpg --- server/routes.ts | 147 ++++++++++++++++++++++++++++++++++++++++++++++ server/storage.ts | 38 ++++++++++++ 2 files changed, 185 insertions(+) diff --git a/server/routes.ts b/server/routes.ts index da69a11..b2c669d 100644 --- a/server/routes.ts +++ b/server/routes.ts @@ -2764,6 +2764,153 @@ export async function registerRoutes(app: Express): Promise { * description: Server error */ + // Domain PATCH endpoint + app.patch("/api/connections/:connectionId/ad-domains/:objectGUID", authenticateApiToken, async (req, res, next) => { + try { + // Check permissions + const hasPermission = await checkPermission(req, PERMISSIONS.UPDATE_AD_DOMAINS); + if (!hasPermission) { + return res.status(403).json({ message: "Not authorized to update domains" }); + } + + const connectionId = parseInt(req.params.connectionId); + const objectGUID = req.params.objectGUID; + + // Verify the connection exists + const connection = await storage.getLdapConnection(connectionId); + if (!connection) { + return res.status(404).json({ message: "LDAP connection not found" }); + } + + // Get domain to update + const domain = await storage.getAdDomain(connectionId, objectGUID); + if (!domain) { + return res.status(404).json({ message: "Domain not found" }); + } + + try { + // Connect to LDAP server + const ldapClient = await connectToLdap(connection); + + // Create modification object based on the request body + const changes = {}; + + if (req.body.description !== undefined) { + changes.description = req.body.description; + } + + if (req.body.netBIOSName !== undefined) { + changes.netBIOSName = req.body.netBIOSName; + } + + // Skip modification if no changes + if (Object.keys(changes).length === 0) { + return res.status(400).json({ message: "No valid attributes provided for update" }); + } + + // Modify domain in LDAP + await ldapClient.modify(domain.distinguishedName, changes); + + // Update domain in database + const updatedDomain = await storage.updateAdDomain(connectionId, objectGUID, { + ...req.body, + adProperties: { ...domain.adProperties, ...changes } + }); + + // Add audit log + await storage.createAuditLogEntry({ + action: "update_domain", + targetId: objectGUID, + userId: req.user?.id, + connectionId: connectionId, + details: { + domain: domain.name, + distinguishedName: domain.distinguishedName, + changes: changes + } + }); + + res.json({ + ...updatedDomain, + objectType: 'domain' + }); + } catch (err) { + console.error("Error updating domain:", err); + return res.status(500).json({ message: `Error updating domain: ${err.message}` }); + } + } catch (error) { + next(error); + } + }); + + // Domain DELETE endpoint + app.delete("/api/connections/:connectionId/ad-domains/:objectGUID", authenticateApiToken, async (req, res, next) => { + try { + // Check permissions + const hasPermission = await checkPermission(req, PERMISSIONS.DELETE_AD_DOMAINS); + if (!hasPermission) { + return res.status(403).json({ message: "Not authorized to delete domains" }); + } + + const connectionId = parseInt(req.params.connectionId); + const objectGUID = req.params.objectGUID; + + // Verify the connection exists + const connection = await storage.getLdapConnection(connectionId); + if (!connection) { + return res.status(404).json({ message: "LDAP connection not found" }); + } + + // Get domain to delete + const domain = await storage.getAdDomain(connectionId, objectGUID); + if (!domain) { + return res.status(404).json({ message: "Domain not found" }); + } + + try { + // Connect to LDAP server + const ldapClient = await connectToLdap(connection); + + // First check if there are any children under this domain + const searchResults = await ldapClient.search(domain.distinguishedName, { + scope: 'one', + filter: '(objectClass=*)' + }); + + if (searchResults.searchEntries && searchResults.searchEntries.length > 0) { + return res.status(409).json({ + message: "Cannot delete domain with child objects. Remove all child objects first." + }); + } + + // Delete domain from LDAP + await ldapClient.del(domain.distinguishedName); + + // Delete domain from database + await storage.deleteAdDomain(connectionId, objectGUID); + + // Add audit log + await storage.createAuditLogEntry({ + action: "delete_domain", + targetId: objectGUID, + userId: req.user?.id, + connectionId: connectionId, + details: { + domain: domain.name, + distinguishedName: domain.distinguishedName + } + }); + + res.json({ success: true }); + } catch (err) { + console.error("Error deleting domain:", err); + return res.status(500).json({ message: `Error deleting domain: ${err.message}` }); + } + } catch (error) { + next(error); + } + }); + app.post("/api/connections/:connectionId/ad-domains", authenticateApiToken, async (req, res, next) => { try { // Check permissions diff --git a/server/storage.ts b/server/storage.ts index 502af6e..8543b33 100644 --- a/server/storage.ts +++ b/server/storage.ts @@ -142,9 +142,12 @@ export interface IStorage { // AD Domains getAdDomain(id: number): Promise; + getAdDomainByGuid(connectionId: number, objectGUID: string): Promise; createAdDomain(domain: InsertAdDomain): Promise; updateAdDomain(id: number, domain: Partial): Promise; + updateAdDomainByGuid(connectionId: number, objectGUID: string, domain: Partial): Promise; deleteAdDomain(id: number): Promise; + deleteAdDomainByGuid(connectionId: number, objectGUID: string): Promise; listAdDomains(connectionId: number, query?: any): Promise; // AD Sites @@ -1027,6 +1030,16 @@ export class DatabaseStorage implements IStorage { const result = await db.select().from(adDomains).where(eq(adDomains.id, id)); return result.length > 0 ? result[0] : undefined; } + + async getAdDomainByGuid(connectionId: number, objectGUID: string): Promise { + const result = await db.select().from(adDomains).where( + and( + eq(adDomains.connectionId, connectionId), + eq(adDomains.objectGUID, objectGUID) + ) + ); + return result.length > 0 ? result[0] : undefined; + } async createAdDomain(domain: InsertAdDomain): Promise { const result = await db.insert(adDomains).values(domain).returning(); @@ -1037,11 +1050,36 @@ export class DatabaseStorage implements IStorage { const result = await db.update(adDomains).set(domainData).where(eq(adDomains.id, id)).returning(); return result.length > 0 ? result[0] : undefined; } + + async updateAdDomainByGuid(connectionId: number, objectGUID: string, domainData: Partial): Promise { + const result = await db.update(adDomains) + .set(domainData) + .where( + and( + eq(adDomains.connectionId, connectionId), + eq(adDomains.objectGUID, objectGUID) + ) + ) + .returning(); + return result.length > 0 ? result[0] : undefined; + } async deleteAdDomain(id: number): Promise { const result = await db.delete(adDomains).where(eq(adDomains.id, id)).returning({ id: adDomains.id }); return result.length > 0; } + + async deleteAdDomainByGuid(connectionId: number, objectGUID: string): Promise { + const result = await db.delete(adDomains) + .where( + and( + eq(adDomains.connectionId, connectionId), + eq(adDomains.objectGUID, objectGUID) + ) + ) + .returning({ id: adDomains.id }); + return result.length > 0; + } async listAdDomains(connectionId: number, query?: any): Promise { let adDomainsQuery = db.select().from(adDomains).where(eq(adDomains.connectionId, connectionId));