Files
DynamoDNS/server/routes.simplified.ts
alphaeusmote 3bb5305872 Add user-customer assignment management API endpoints.
Replit-Commit-Author: Agent
Replit-Commit-Session-Id: 9111ef36-26c8-4085-84ca-a35dc1fec1b5
Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/7083d608-d6d3-4a6a-9a27-6286c5109627/f61cca86-ab28-414a-8579-18e14f115a0f.jpg
2025-04-11 20:03:33 +00:00

759 lines
26 KiB
TypeScript

import type { Express, Request } from "express";
import { createServer, type Server } from "http";
import { storage } from "./storage";
import { getCurrentIpAddress, getCurrentIpv6Address } from "./utils/ip-utils";
import { setupAuth, requireRole } from "./auth";
import { z } from "zod";
import {
insertCustomerSchema,
insertDomainSchema,
insertDnsRecordSchema,
insertCustomerUserAssignmentSchema,
insertProviderSchema,
insertDomainCredentialsSchema,
insertApiTokenSchema,
insertCustomRoleSchema
} from "@shared/schema";
export async function registerRoutes(app: Express): Promise<Server> {
// Create HTTP server
const httpServer = createServer(app);
// Setup authentication routes
setupAuth(app);
// Add public IP endpoint for the frontend
app.get('/api/public-ip', async (req, res) => {
try {
console.log('Fetching public IP addresses');
// Get IPv4 address using our utility function
const ipv4 = await getCurrentIpAddress();
console.log(`Resolved IPv4: ${ipv4}`);
// Try to get IPv6 as well
const ipv6 = await getCurrentIpv6Address();
console.log(`Resolved IPv6: ${ipv6}`);
res.json({ ipv4, ipv6 });
} catch (error) {
console.error('Error getting public IP:', error);
res.status(500).json({ error: 'Failed to get public IP address' });
}
});
// Customer routes
app.get("/api/customers", requireRole(["admin", "manager"]), async (req, res) => {
try {
const customers = await storage.getCustomers();
res.json(customers);
} catch (error) {
console.error("Error getting customers:", error);
res.status(500).json({ message: "Internal server error" });
}
});
app.get("/api/customers/:id", requireRole(["admin", "manager", "user", "readonly"]), async (req, res) => {
try {
const customer = await storage.getCustomer(req.params.id);
if (!customer) {
return res.status(404).json({ message: "Customer not found" });
}
res.json(customer);
} catch (error) {
console.error("Error getting customer:", error);
res.status(500).json({ message: "Internal server error" });
}
});
app.post("/api/customers", requireRole(["admin"]), async (req, res) => {
try {
const validatedData = insertCustomerSchema.parse(req.body);
const customer = await storage.createCustomer(validatedData);
res.status(201).json(customer);
} catch (error) {
if (error instanceof z.ZodError) {
res.status(400).json({ message: "Validation error", errors: error.errors });
} else {
console.error("Error creating customer:", error);
res.status(500).json({ message: "Internal server error" });
}
}
});
app.put("/api/customers/:id", requireRole(["admin"]), async (req, res) => {
try {
const validatedData = insertCustomerSchema.partial().parse(req.body);
const customer = await storage.updateCustomer(req.params.id, validatedData);
if (!customer) {
return res.status(404).json({ message: "Customer not found" });
}
res.json(customer);
} catch (error) {
if (error instanceof z.ZodError) {
res.status(400).json({ message: "Validation error", errors: error.errors });
} else {
console.error("Error updating customer:", error);
res.status(500).json({ message: "Internal server error" });
}
}
});
app.delete("/api/customers/:id", requireRole(["admin"]), async (req, res) => {
try {
const success = await storage.deleteCustomer(req.params.id);
if (!success) {
return res.status(404).json({ message: "Customer not found" });
}
res.status(204).end();
} catch (error) {
console.error("Error deleting customer:", error);
res.status(500).json({ message: "Internal server error" });
}
});
// Domain routes
app.get("/api/domains", requireRole(["admin", "manager", "user", "readonly"]), async (req, res) => {
try {
const { customerId } = req.query;
let domains;
if (customerId) {
domains = await storage.getDomainsByCustomer(customerId as string);
} else {
domains = await storage.getAllDomains();
}
res.json(domains);
} catch (error) {
console.error("Error getting 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 domain = await storage.getDomain(req.params.id);
if (!domain) {
return res.status(404).json({ message: "Domain not found" });
}
res.json(domain);
} catch (error) {
console.error("Error getting 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 validatedData = insertDomainSchema.partial().parse(req.body);
const domain = await storage.updateDomain(req.params.id, validatedData);
if (!domain) {
return res.status(404).json({ message: "Domain not found" });
}
res.json(domain);
} 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 success = await storage.deleteDomain(req.params.id);
if (!success) {
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 routes
app.get("/api/dns-records", requireRole(["admin", "manager", "user", "readonly"]), async (req, res) => {
try {
const { domainId } = req.query;
if (!domainId) {
return res.status(400).json({ message: "Domain ID is required" });
}
const records = await storage.getDnsRecordsByDomain(domainId as string);
res.json(records);
} catch (error) {
console.error("Error getting 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 record = await storage.getDnsRecord(req.params.id);
if (!record) {
return res.status(404).json({ message: "DNS record not found" });
}
res.json(record);
} catch (error) {
console.error("Error getting 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 and parse input
const validatedData = insertDnsRecordSchema.parse(req.body);
// Special handling for AutoIP records
if (validatedData.isAutoIP) {
try {
// Get the current IPv4 address
const ipv4 = await getCurrentIpAddress();
// If this is an A record, update the content with the current IP
if (validatedData.type === "A") {
validatedData.content = ipv4;
}
// If this is an AAAA record, try to get IPv6 address
if (validatedData.type === "AAAA") {
const ipv6 = await getCurrentIpv6Address();
if (ipv6) {
validatedData.content = ipv6;
} else {
// If no IPv6 available, return an error
return res.status(400).json({
message: "Cannot create an AAAA record with Auto IP enabled - no IPv6 address detected"
});
}
}
} catch (ipError) {
console.error("Error getting IP for AutoIP record:", ipError);
}
}
// Create the record
const record = await storage.createDnsRecord(validatedData);
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 {
// Validate and parse input
const validatedData = insertDnsRecordSchema.partial().parse(req.body);
// Special handling for AutoIP records
if (validatedData.isAutoIP) {
// Get the current record to check its type
const currentRecord = await storage.getDnsRecord(req.params.id);
if (!currentRecord) {
return res.status(404).json({ message: "DNS record not found" });
}
try {
// Get the current IPv4 address
const ipv4 = await getCurrentIpAddress();
// If this is an A record, update the content with the current IP
if (currentRecord.type === "A") {
validatedData.content = ipv4;
}
// If this is an AAAA record, try to get IPv6 address
if (currentRecord.type === "AAAA") {
const ipv6 = await getCurrentIpv6Address();
if (ipv6) {
validatedData.content = ipv6;
} else {
// If no IPv6 available, return an error
return res.status(400).json({
message: "Cannot update an AAAA record with Auto IP enabled - no IPv6 address detected"
});
}
}
} catch (ipError) {
console.error("Error getting IP for AutoIP record:", ipError);
}
}
// Update the record
const record = await storage.updateDnsRecord(req.params.id, validatedData);
if (!record) {
return res.status(404).json({ message: "DNS record not found" });
}
res.json(record);
} 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 success = await storage.deleteDnsRecord(req.params.id);
if (!success) {
return res.status(404).json({ message: "DNS record not found" });
}
res.status(204).end();
} catch (error) {
console.error("Error deleting DNS record:", error);
res.status(500).json({ message: "Internal server error" });
}
});
// Customer-User Assignment routes - for managing user access to customers
app.get("/api/customer-users", requireRole(["admin", "manager"]), async (req, res) => {
try {
const { customerId, userId } = req.query;
let assignments = [];
if (customerId) {
// Get all users assigned to a specific customer
assignments = await storage.getCustomerUsers(customerId as string);
} else if (userId) {
// Get all customers assigned to a specific user
assignments = await storage.getUserCustomers(userId as string);
} else {
return res.status(400).json({ message: "Either customerId or userId query parameter is required" });
}
res.json(assignments);
} catch (error) {
console.error("Error getting customer-user assignments:", error);
res.status(500).json({ message: "Internal server error" });
}
});
app.post("/api/customer-users", requireRole(["admin"]), async (req, res) => {
try {
const validatedData = insertCustomerUserAssignmentSchema.parse(req.body);
const assignment = await storage.assignUserToCustomer(validatedData);
res.status(201).json(assignment);
} catch (error) {
if (error instanceof z.ZodError) {
res.status(400).json({ message: "Validation error", errors: error.errors });
} else {
console.error("Error creating customer-user assignment:", error);
res.status(500).json({ message: "Internal server error" });
}
}
});
app.put("/api/customer-users", requireRole(["admin"]), async (req, res) => {
try {
const { customerId, userId, role } = req.body;
if (!customerId || !userId || !role) {
return res.status(400).json({ message: "customerId, userId, and role are required" });
}
const assignment = await storage.updateUserCustomerRole(customerId, userId, role);
if (!assignment) {
return res.status(404).json({ message: "Customer-user assignment not found" });
}
res.json(assignment);
} catch (error) {
console.error("Error updating customer-user role:", error);
res.status(500).json({ message: "Internal server error" });
}
});
app.delete("/api/customer-users", requireRole(["admin"]), async (req, res) => {
try {
const { customerId, userId } = req.query;
if (!customerId || !userId) {
return res.status(400).json({ message: "customerId and userId query parameters are required" });
}
const success = await storage.removeUserFromCustomer(customerId as string, userId as string);
if (!success) {
return res.status(404).json({ message: "Customer-user assignment not found" });
}
res.status(204).end();
} catch (error) {
console.error("Error deleting customer-user assignment:", error);
res.status(500).json({ message: "Internal server error" });
}
});
// Provider routes
app.get("/api/providers", requireRole(["admin", "manager", "user", "readonly"]), async (req, res) => {
try {
const providers = await storage.getProviders();
res.json(providers);
} catch (error) {
console.error("Error getting providers:", error);
res.status(500).json({ message: "Internal server error" });
}
});
app.get("/api/providers/:id", requireRole(["admin", "manager", "user", "readonly"]), async (req, res) => {
try {
const provider = await storage.getProvider(req.params.id);
if (!provider) {
return res.status(404).json({ message: "Provider not found" });
}
res.json(provider);
} catch (error) {
console.error("Error getting provider:", error);
res.status(500).json({ message: "Internal server error" });
}
});
app.post("/api/providers", requireRole(["admin"]), async (req, res) => {
try {
const validatedData = insertProviderSchema.parse(req.body);
const provider = await storage.createProvider(validatedData);
res.status(201).json(provider);
} 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 validatedData = insertProviderSchema.partial().parse(req.body);
const provider = await storage.updateProvider(req.params.id, validatedData);
if (!provider) {
return res.status(404).json({ message: "Provider not found" });
}
res.json(provider);
} 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 success = await storage.deleteProvider(req.params.id);
if (!success) {
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" });
}
});
// Domain Credentials routes
app.get("/api/domain-credentials/:domainId", requireRole(["admin", "manager"]), async (req, res) => {
try {
const credentials = await storage.getDomainCredentials(req.params.domainId);
if (!credentials) {
return res.status(404).json({ message: "Domain credentials not found" });
}
res.json(credentials);
} catch (error) {
console.error("Error getting domain credentials:", error);
res.status(500).json({ message: "Internal server error" });
}
});
app.post("/api/domain-credentials", requireRole(["admin", "manager"]), async (req, res) => {
try {
const validatedData = insertDomainCredentialsSchema.parse(req.body);
const credentials = await storage.createDomainCredentials(validatedData);
res.status(201).json(credentials);
} catch (error) {
if (error instanceof z.ZodError) {
res.status(400).json({ message: "Validation error", errors: error.errors });
} else {
console.error("Error creating domain credentials:", error);
res.status(500).json({ message: "Internal server error" });
}
}
});
app.put("/api/domain-credentials/:domainId", requireRole(["admin", "manager"]), async (req, res) => {
try {
const validatedData = insertDomainCredentialsSchema.partial().parse(req.body);
const credentials = await storage.updateDomainCredentials(req.params.domainId, validatedData);
if (!credentials) {
return res.status(404).json({ message: "Domain credentials not found" });
}
res.json(credentials);
} catch (error) {
if (error instanceof z.ZodError) {
res.status(400).json({ message: "Validation error", errors: error.errors });
} else {
console.error("Error updating domain credentials:", error);
res.status(500).json({ message: "Internal server error" });
}
}
});
// API Token routes
app.get("/api/api-tokens", requireRole(["admin"]), async (req, res) => {
try {
const { customerId } = req.query;
let tokens = [];
if (customerId) {
tokens = await storage.getApiTokensByCustomer(customerId as string);
} else {
tokens = await storage.getApiTokens();
}
// Mask the actual token values for security
const maskedTokens = tokens.map(token => ({
...token,
token: token.token ? token.token.substring(0, 6) + "************" : null
}));
res.json(maskedTokens);
} catch (error) {
console.error("Error getting API tokens:", error);
res.status(500).json({ message: "Internal server error" });
}
});
app.get("/api/api-tokens/:id", requireRole(["admin"]), async (req, res) => {
try {
const token = await storage.getApiToken(req.params.id);
if (!token) {
return res.status(404).json({ message: "API token not found" });
}
// Mask the actual token value for security
const maskedToken = {
...token,
token: token.token ? token.token.substring(0, 6) + "************" : null
};
res.json(maskedToken);
} catch (error) {
console.error("Error getting API token:", error);
res.status(500).json({ message: "Internal server error" });
}
});
app.post("/api/api-tokens", requireRole(["admin"]), async (req, res) => {
try {
const validatedData = insertApiTokenSchema.parse(req.body);
// Add the current user as the creator if not specified
if (!validatedData.createdBy && req.user) {
validatedData.createdBy = req.user.id;
}
const token = await storage.createApiToken(validatedData);
// Return the full token value only once during 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"]), async (req, res) => {
try {
const validatedData = insertApiTokenSchema.partial().parse(req.body);
const token = await storage.updateApiToken(req.params.id, validatedData);
if (!token) {
return res.status(404).json({ message: "API token not found" });
}
// Mask the token value for security in the response
const maskedToken = {
...token,
token: token.token ? token.token.substring(0, 6) + "************" : null
};
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"]), async (req, res) => {
try {
const success = await storage.deleteApiToken(req.params.id);
if (!success) {
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" });
}
});
// API Token Customer Access routes
app.get("/api/api-token-access/:tokenId", requireRole(["admin"]), async (req, res) => {
try {
const access = await storage.getApiTokenCustomerAccess(req.params.tokenId);
res.json(access);
} catch (error) {
console.error("Error getting API token customer access:", error);
res.status(500).json({ message: "Internal server error" });
}
});
app.post("/api/api-token-access", requireRole(["admin"]), async (req, res) => {
try {
const { tokenId, customerId } = req.body;
if (!tokenId || !customerId) {
return res.status(400).json({ message: "tokenId and customerId are required" });
}
const access = await storage.addApiTokenCustomerAccess({
tokenId,
customerId
});
res.status(201).json(access);
} catch (error) {
console.error("Error adding API token customer access:", error);
res.status(500).json({ message: "Internal server error" });
}
});
app.delete("/api/api-token-access", requireRole(["admin"]), async (req, res) => {
try {
const { tokenId, customerId } = req.query;
if (!tokenId || !customerId) {
return res.status(400).json({ message: "tokenId and customerId query parameters are required" });
}
const success = await storage.removeApiTokenCustomerAccess(tokenId as string, customerId as string);
if (!success) {
return res.status(404).json({ message: "API token customer access not found" });
}
res.status(204).end();
} catch (error) {
console.error("Error removing API token customer access:", error);
res.status(500).json({ message: "Internal server error" });
}
});
// Custom Roles routes
app.get("/api/custom-roles", requireRole(["admin"]), async (req, res) => {
try {
const roles = await storage.getCustomRoles();
res.json(roles);
} catch (error) {
console.error("Error getting custom roles:", error);
res.status(500).json({ message: "Internal server error" });
}
});
app.get("/api/custom-roles/:id", requireRole(["admin"]), async (req, res) => {
try {
const role = await storage.getCustomRole(req.params.id);
if (!role) {
return res.status(404).json({ message: "Custom role not found" });
}
res.json(role);
} catch (error) {
console.error("Error getting custom role:", error);
res.status(500).json({ message: "Internal server error" });
}
});
app.post("/api/custom-roles", requireRole(["admin"]), async (req, res) => {
try {
const validatedData = insertCustomRoleSchema.parse(req.body);
const role = await storage.createCustomRole(validatedData);
res.status(201).json(role);
} catch (error) {
if (error instanceof z.ZodError) {
res.status(400).json({ message: "Validation error", errors: error.errors });
} else {
console.error("Error creating custom role:", error);
res.status(500).json({ message: "Internal server error" });
}
}
});
app.put("/api/custom-roles/:id", requireRole(["admin"]), async (req, res) => {
try {
const validatedData = insertCustomRoleSchema.partial().parse(req.body);
const role = await storage.updateCustomRole(req.params.id, validatedData);
if (!role) {
return res.status(404).json({ message: "Custom role not found" });
}
res.json(role);
} catch (error) {
if (error instanceof z.ZodError) {
res.status(400).json({ message: "Validation error", errors: error.errors });
} else {
console.error("Error updating custom role:", error);
res.status(500).json({ message: "Internal server error" });
}
}
});
app.delete("/api/custom-roles/:id", requireRole(["admin"]), async (req, res) => {
try {
const success = await storage.deleteCustomRole(req.params.id);
if (!success) {
return res.status(404).json({ message: "Custom role not found" });
}
res.status(204).end();
} catch (error) {
console.error("Error deleting custom role:", error);
res.status(500).json({ message: "Internal server error" });
}
});
return httpServer;
}