import type { Express, Request, Response } from "express"; import { createServer, type Server } from "http"; import { storage } from "./storage"; import { setupAuth, requireRole } from "./auth"; import { z } from "zod"; import { insertDomainSchema, insertDnsRecordSchema, insertProviderSchema, insertApiTokenSchema, insertOrganizationSchema, recordTypes, providerTypes } from "@shared/schema"; import { randomBytes } from "crypto"; export async function registerRoutes(app: Express): Promise { // Setup authentication routes setupAuth(app); const httpServer = createServer(app); // Organizations app.get("/api/organizations", requireRole(["admin", "manager", "user", "readonly"]), async (req, res) => { try { const organizations = await storage.getOrganizations(); res.json(organizations); } catch (error) { console.error("Error fetching organizations:", error); res.status(500).json({ message: "Internal server error" }); } }); app.get("/api/organizations/:id", requireRole(["admin", "manager", "user", "readonly"]), async (req, res) => { try { const id = parseInt(req.params.id); const organization = await storage.getOrganization(id); if (!organization) { return res.status(404).json({ message: "Organization not found" }); } res.json(organization); } catch (error) { console.error("Error fetching organization:", error); res.status(500).json({ message: "Internal server error" }); } }); app.post("/api/organizations", requireRole(["admin"]), async (req, res) => { try { const validatedData = insertOrganizationSchema.parse(req.body); const organization = await storage.createOrganization(validatedData); res.status(201).json(organization); } catch (error) { if (error instanceof z.ZodError) { res.status(400).json({ message: "Validation error", errors: error.errors }); } else { console.error("Error creating organization:", error); res.status(500).json({ message: "Internal server error" }); } } }); app.put("/api/organizations/:id", requireRole(["admin"]), async (req, res) => { try { const id = parseInt(req.params.id); const validatedData = insertOrganizationSchema.partial().parse(req.body); const updatedOrganization = await storage.updateOrganization(id, validatedData); if (!updatedOrganization) { return res.status(404).json({ message: "Organization not found" }); } res.json(updatedOrganization); } catch (error) { if (error instanceof z.ZodError) { res.status(400).json({ message: "Validation error", errors: error.errors }); } else { console.error("Error updating organization:", error); res.status(500).json({ message: "Internal server error" }); } } }); app.delete("/api/organizations/:id", requireRole(["admin"]), async (req, res) => { try { const id = parseInt(req.params.id); const deleted = await storage.deleteOrganization(id); if (!deleted) { return res.status(404).json({ message: "Organization not found" }); } res.status(204).end(); } catch (error) { console.error("Error deleting organization:", error); res.status(500).json({ message: "Internal server error" }); } }); // Domains app.get("/api/domains", requireRole(["admin", "manager", "user", "readonly"]), async (req, res) => { try { let domains; const orgId = req.query.organizationId ? parseInt(req.query.organizationId as string) : undefined; if (orgId) { domains = await storage.getDomainsByOrganization(orgId); } else { domains = await storage.getAllDomains(); } res.json(domains); } catch (error) { console.error("Error fetching domains:", error); res.status(500).json({ message: "Internal server error" }); } }); app.get("/api/domains/:id", requireRole(["admin", "manager", "user", "readonly"]), async (req, res) => { try { const id = parseInt(req.params.id); const domain = await storage.getDomain(id); if (!domain) { return res.status(404).json({ message: "Domain not found" }); } res.json(domain); } catch (error) { console.error("Error fetching domain:", error); res.status(500).json({ message: "Internal server error" }); } }); app.post("/api/domains", requireRole(["admin", "manager"]), async (req, res) => { try { const validatedData = insertDomainSchema.parse(req.body); const domain = await storage.createDomain(validatedData); res.status(201).json(domain); } catch (error) { if (error instanceof z.ZodError) { res.status(400).json({ message: "Validation error", errors: error.errors }); } else { console.error("Error creating domain:", error); res.status(500).json({ message: "Internal server error" }); } } }); app.put("/api/domains/:id", requireRole(["admin", "manager"]), async (req, res) => { try { const id = parseInt(req.params.id); const validatedData = insertDomainSchema.partial().parse(req.body); const updatedDomain = await storage.updateDomain(id, validatedData); if (!updatedDomain) { return res.status(404).json({ message: "Domain not found" }); } res.json(updatedDomain); } catch (error) { if (error instanceof z.ZodError) { res.status(400).json({ message: "Validation error", errors: error.errors }); } else { console.error("Error updating domain:", error); res.status(500).json({ message: "Internal server error" }); } } }); app.delete("/api/domains/:id", requireRole(["admin", "manager"]), async (req, res) => { try { const id = parseInt(req.params.id); const deleted = await storage.deleteDomain(id); if (!deleted) { return res.status(404).json({ message: "Domain not found" }); } res.status(204).end(); } catch (error) { console.error("Error deleting domain:", error); res.status(500).json({ message: "Internal server error" }); } }); // DNS Records app.get("/api/dns-records", requireRole(["admin", "manager", "user", "readonly"]), async (req, res) => { try { const domainId = req.query.domainId ? parseInt(req.query.domainId as string) : undefined; if (!domainId) { return res.status(400).json({ message: "Domain ID is required" }); } const records = await storage.getDnsRecordsByDomain(domainId); res.json(records); } catch (error) { console.error("Error fetching DNS records:", error); res.status(500).json({ message: "Internal server error" }); } }); app.get("/api/dns-records/:id", requireRole(["admin", "manager", "user", "readonly"]), async (req, res) => { try { const id = parseInt(req.params.id); const record = await storage.getDnsRecord(id); if (!record) { return res.status(404).json({ message: "DNS record not found" }); } res.json(record); } catch (error) { console.error("Error fetching DNS record:", error); res.status(500).json({ message: "Internal server error" }); } }); app.post("/api/dns-records", requireRole(["admin", "manager", "user"]), async (req, res) => { try { // Validate record type const recordTypeValidator = z.enum(recordTypes); // Extend schema with validation const schema = insertDnsRecordSchema.extend({ type: recordTypeValidator }); const validatedData = schema.parse(req.body); // Get the previous record if it exists let previousRecord = null; const records = await storage.getDnsRecordsByDomain(validatedData.domainId); previousRecord = records.find(r => r.name === validatedData.name && r.type === validatedData.type); // Create the new record const record = await storage.createDnsRecord(validatedData); // Add to history await storage.addDnsHistory( record.id, "create", previousRecord ? JSON.stringify(previousRecord) : undefined, JSON.stringify(record), req.user?.id ); res.status(201).json(record); } catch (error) { if (error instanceof z.ZodError) { res.status(400).json({ message: "Validation error", errors: error.errors }); } else { console.error("Error creating DNS record:", error); res.status(500).json({ message: "Internal server error" }); } } }); app.put("/api/dns-records/:id", requireRole(["admin", "manager", "user"]), async (req, res) => { try { const id = parseInt(req.params.id); // Get the previous record const previousRecord = await storage.getDnsRecord(id); if (!previousRecord) { return res.status(404).json({ message: "DNS record not found" }); } // Validate record type const recordTypeValidator = z.enum(recordTypes); // Extend schema with validation const schema = insertDnsRecordSchema.partial().extend({ type: recordTypeValidator.optional() }); const validatedData = schema.parse(req.body); // Update the record const updatedRecord = await storage.updateDnsRecord(id, validatedData); if (!updatedRecord) { return res.status(404).json({ message: "DNS record not found" }); } // Add to history await storage.addDnsHistory( id, "update", JSON.stringify(previousRecord), JSON.stringify(updatedRecord), req.user?.id ); res.json(updatedRecord); } catch (error) { if (error instanceof z.ZodError) { res.status(400).json({ message: "Validation error", errors: error.errors }); } else { console.error("Error updating DNS record:", error); res.status(500).json({ message: "Internal server error" }); } } }); app.delete("/api/dns-records/:id", requireRole(["admin", "manager"]), async (req, res) => { try { const id = parseInt(req.params.id); // Get the record before deletion const record = await storage.getDnsRecord(id); if (!record) { return res.status(404).json({ message: "DNS record not found" }); } const deleted = await storage.deleteDnsRecord(id); if (!deleted) { return res.status(404).json({ message: "DNS record not found" }); } // Add to history await storage.addDnsHistory( id, "delete", JSON.stringify(record), undefined, req.user?.id ); res.status(204).end(); } catch (error) { console.error("Error deleting DNS record:", error); res.status(500).json({ message: "Internal server error" }); } }); // Providers app.get("/api/providers", requireRole(["admin", "manager", "user", "readonly"]), async (req, res) => { try { const providers = await storage.getProviders(); // Mask credentials in response const maskedProviders = providers.map(provider => { if (provider.credentials) { return { ...provider, credentials: { masked: true } }; } return provider; }); res.json(maskedProviders); } catch (error) { console.error("Error fetching providers:", error); res.status(500).json({ message: "Internal server error" }); } }); app.get("/api/providers/:id", requireRole(["admin", "manager"]), async (req, res) => { try { const id = parseInt(req.params.id); const provider = await storage.getProvider(id); if (!provider) { return res.status(404).json({ message: "Provider not found" }); } // Mask credentials in response const maskedProvider = { ...provider, credentials: provider.credentials ? { masked: true } : null }; res.json(maskedProvider); } catch (error) { console.error("Error fetching provider:", error); res.status(500).json({ message: "Internal server error" }); } }); app.post("/api/providers", requireRole(["admin"]), async (req, res) => { try { // Validate provider type const providerTypeValidator = z.enum(providerTypes); // Extend schema with validation const schema = insertProviderSchema.extend({ type: providerTypeValidator }); const validatedData = schema.parse(req.body); const provider = await storage.createProvider(validatedData); // Mask credentials in response const maskedProvider = { ...provider, credentials: provider.credentials ? { masked: true } : null }; res.status(201).json(maskedProvider); } catch (error) { if (error instanceof z.ZodError) { res.status(400).json({ message: "Validation error", errors: error.errors }); } else { console.error("Error creating provider:", error); res.status(500).json({ message: "Internal server error" }); } } }); app.put("/api/providers/:id", requireRole(["admin"]), async (req, res) => { try { const id = parseInt(req.params.id); // Validate provider type const providerTypeValidator = z.enum(providerTypes); // Extend schema with validation const schema = insertProviderSchema.partial().extend({ type: providerTypeValidator.optional() }); const validatedData = schema.parse(req.body); const updatedProvider = await storage.updateProvider(id, validatedData); if (!updatedProvider) { return res.status(404).json({ message: "Provider not found" }); } // Mask credentials in response const maskedProvider = { ...updatedProvider, credentials: updatedProvider.credentials ? { masked: true } : null }; res.json(maskedProvider); } catch (error) { if (error instanceof z.ZodError) { res.status(400).json({ message: "Validation error", errors: error.errors }); } else { console.error("Error updating provider:", error); res.status(500).json({ message: "Internal server error" }); } } }); app.delete("/api/providers/:id", requireRole(["admin"]), async (req, res) => { try { const id = parseInt(req.params.id); const deleted = await storage.deleteProvider(id); if (!deleted) { return res.status(404).json({ message: "Provider not found" }); } res.status(204).end(); } catch (error) { console.error("Error deleting provider:", error); res.status(500).json({ message: "Internal server error" }); } }); // API Tokens app.get("/api/api-tokens", requireRole(["admin", "manager"]), async (req, res) => { try { let tokens; const orgId = req.query.organizationId ? parseInt(req.query.organizationId as string) : undefined; if (orgId) { tokens = await storage.getApiTokensByOrganization(orgId); } else if (req.user?.role === "admin") { // Admins can see all tokens const allOrgs = await storage.getOrganizations(); tokens = await Promise.all( allOrgs.map(org => storage.getApiTokensByOrganization(org.id)) ).then(results => results.flat()); } else { // Others can only see tokens for their organization tokens = req.user?.organizationId ? await storage.getApiTokensByOrganization(req.user.organizationId) : []; } // Mask token values const maskedTokens = tokens.map(token => ({ ...token, token: token.token.substring(0, 8) + '...' })); res.json(maskedTokens); } catch (error) { console.error("Error fetching API tokens:", error); res.status(500).json({ message: "Internal server error" }); } }); app.post("/api/api-tokens", requireRole(["admin", "manager"]), async (req, res) => { try { // Generate a random token const tokenValue = randomBytes(32).toString('hex'); // Validate permissions const permissionsValidator = z.array(z.enum(["admin", "manager", "user", "readonly"])); // Create schema with additional validation const schema = insertApiTokenSchema .omit({ token: true }) .extend({ permissions: permissionsValidator, name: z.string().min(1) }); const validatedData = schema.parse(req.body); // Set token value and created by const tokenData = { ...validatedData, token: tokenValue, createdBy: req.user?.id }; const token = await storage.createApiToken(tokenData); // Return the full token only on creation res.status(201).json(token); } catch (error) { if (error instanceof z.ZodError) { res.status(400).json({ message: "Validation error", errors: error.errors }); } else { console.error("Error creating API token:", error); res.status(500).json({ message: "Internal server error" }); } } }); app.put("/api/api-tokens/:id", requireRole(["admin", "manager"]), async (req, res) => { try { const id = parseInt(req.params.id); // Get the token const token = await storage.getApiToken(id); if (!token) { return res.status(404).json({ message: "API token not found" }); } // Verify permission: only admin can modify any token, managers can only modify their org's tokens if (req.user?.role !== "admin" && token.organizationId !== req.user?.organizationId) { return res.status(403).json({ message: "Insufficient permissions" }); } // Validate permissions const permissionsValidator = z.array(z.enum(["admin", "manager", "user", "readonly"])).optional(); // Create schema with additional validation const schema = insertApiTokenSchema .omit({ token: true }) .partial() .extend({ permissions: permissionsValidator, name: z.string().min(1).optional() }); const validatedData = schema.parse(req.body); const updatedToken = await storage.updateApiToken(id, validatedData); if (!updatedToken) { return res.status(404).json({ message: "API token not found" }); } // Mask token in response const maskedToken = { ...updatedToken, token: updatedToken.token.substring(0, 8) + '...' }; res.json(maskedToken); } catch (error) { if (error instanceof z.ZodError) { res.status(400).json({ message: "Validation error", errors: error.errors }); } else { console.error("Error updating API token:", error); res.status(500).json({ message: "Internal server error" }); } } }); app.delete("/api/api-tokens/:id", requireRole(["admin", "manager"]), async (req, res) => { try { const id = parseInt(req.params.id); // Get the token const token = await storage.getApiToken(id); if (!token) { return res.status(404).json({ message: "API token not found" }); } // Verify permission: only admin can delete any token, managers can only delete their org's tokens if (req.user?.role !== "admin" && token.organizationId !== req.user?.organizationId) { return res.status(403).json({ message: "Insufficient permissions" }); } const deleted = await storage.deleteApiToken(id); if (!deleted) { return res.status(404).json({ message: "API token not found" }); } res.status(204).end(); } catch (error) { console.error("Error deleting API token:", error); res.status(500).json({ message: "Internal server error" }); } }); // DNS History app.get("/api/dns-history/record/:recordId", requireRole(["admin", "manager", "user", "readonly"]), async (req, res) => { try { const recordId = parseInt(req.params.recordId); const history = await storage.getDnsHistoryByRecord(recordId); res.json(history); } catch (error) { console.error("Error fetching DNS history:", error); res.status(500).json({ message: "Internal server error" }); } }); app.get("/api/dns-history/domain/:domainId", requireRole(["admin", "manager", "user", "readonly"]), async (req, res) => { try { const domainId = parseInt(req.params.domainId); const history = await storage.getDnsHistoryByDomain(domainId); res.json(history); } catch (error) { console.error("Error fetching DNS history:", error); res.status(500).json({ message: "Internal server error" }); } }); // User Management (admin only) app.get("/api/users", requireRole(["admin"]), async (req, res) => { try { // This would use a real database query to get all users // For the in-memory storage, we'll return the currently stored users const users = Array.from(storage.usersMap ? storage.usersMap.values() : []); // Remove passwords from response const sanitizedUsers = users.map(user => { const { password, ...userWithoutPassword } = user; return userWithoutPassword; }); res.json(sanitizedUsers); } catch (error) { console.error("Error fetching users:", error); res.status(500).json({ message: "Internal server error" }); } }); return httpServer; }