diff --git a/server/auth/actions.ts b/server/auth/actions.ts index 4e2738e1..4c442d2c 100644 --- a/server/auth/actions.ts +++ b/server/auth/actions.ts @@ -82,6 +82,7 @@ export enum ActionsEnum { getClient = "getClient", listOrgDomains = "listOrgDomains", getDomain = "getDomain", + getDNSRecords = "getDNSRecords", createNewt = "createNewt", createIdp = "createIdp", updateIdp = "updateIdp", diff --git a/server/db/sqlite/schema/schema.ts b/server/db/sqlite/schema/schema.ts index 30841a4b..bc1ce81b 100644 --- a/server/db/sqlite/schema/schema.ts +++ b/server/db/sqlite/schema/schema.ts @@ -16,6 +16,18 @@ export const domains = sqliteTable("domains", { preferWildcardCert: integer("preferWildcardCert", { mode: "boolean" }) }); +export const dnsRecords = sqliteTable("dnsRecords", { + id: text("id").primaryKey(), + domainId: text("domainId") + .notNull() + .references(() => domains.domainId, { onDelete: "cascade" }), + + recordType: text("recordType").notNull(), // "NS" | "CNAME" | "A" | "TXT" + baseDomain: text("baseDomain"), + value: text("value").notNull(), +}); + + export const orgs = sqliteTable("orgs", { orgId: text("orgId").primaryKey(), name: text("name").notNull(), @@ -748,6 +760,7 @@ export type ResourceWhitelist = InferSelectModel; export type VersionMigration = InferSelectModel; export type ResourceRule = InferSelectModel; export type Domain = InferSelectModel; +export type DnsRecord = InferSelectModel; export type Client = InferSelectModel; export type ClientSite = InferSelectModel; export type RoleClient = InferSelectModel; diff --git a/server/routers/domain/createOrgDomain.ts b/server/routers/domain/createOrgDomain.ts index b35a3de8..1f1002af 100644 --- a/server/routers/domain/createOrgDomain.ts +++ b/server/routers/domain/createOrgDomain.ts @@ -1,6 +1,6 @@ import { Request, Response, NextFunction } from "express"; import { z } from "zod"; -import { db, Domain, domains, OrgDomains, orgDomains } from "@server/db"; +import { db, Domain, domains, OrgDomains, orgDomains, dnsRecords } from "@server/db"; import response from "@server/lib/response"; import HttpCode from "@server/types/HttpCode"; import createHttpError from "http-errors"; @@ -276,9 +276,23 @@ export async function createOrgDomain( }) .returning(); + // Prepare DNS records to insert + const recordsToInsert = []; + // TODO: This needs to be cross region and not hardcoded if (type === "ns") { nsRecords = config.getRawConfig().dns.nameservers as string[]; + + // Save NS records to database + for (const nsValue of nsRecords) { + recordsToInsert.push({ + id: generateId(15), + domainId, + recordType: "NS", + baseDomain: baseDomain, + value: nsValue + }); + } } else if (type === "cname") { cnameRecords = [ { @@ -290,6 +304,17 @@ export async function createOrgDomain( baseDomain: `_acme-challenge.${baseDomain}` } ]; + + // Save CNAME records to database + for (const cnameRecord of cnameRecords) { + recordsToInsert.push({ + id: generateId(15), + domainId, + recordType: "CNAME", + baseDomain: cnameRecord.baseDomain, + value: cnameRecord.value + }); + } } else if (type === "wildcard") { aRecords = [ { @@ -301,6 +326,22 @@ export async function createOrgDomain( baseDomain: `${baseDomain}` } ]; + + // Save A records to database + for (const aRecord of aRecords) { + recordsToInsert.push({ + id: generateId(15), + domainId, + recordType: "A", + baseDomain: aRecord.baseDomain, + value: aRecord.value + }); + } + } + + // Insert all DNS records in batch + if (recordsToInsert.length > 0) { + await trx.insert(dnsRecords).values(recordsToInsert); } numOrgDomains = await trx diff --git a/server/routers/domain/getDNSRecords.ts b/server/routers/domain/getDNSRecords.ts new file mode 100644 index 00000000..ee349cdd --- /dev/null +++ b/server/routers/domain/getDNSRecords.ts @@ -0,0 +1,86 @@ +import { Request, Response, NextFunction } from "express"; +import { z } from "zod"; +import { db, dnsRecords } from "@server/db"; +import { eq } from "drizzle-orm"; +import response from "@server/lib/response"; +import HttpCode from "@server/types/HttpCode"; +import createHttpError from "http-errors"; +import logger from "@server/logger"; +import { fromError } from "zod-validation-error"; +import { OpenAPITags, registry } from "@server/openApi"; + +const getDNSRecordsSchema = z + .object({ + domainId: z.string(), + orgId: z.string() + }) + .strict(); + +async function query(domainId: string) { + const records = await db + .select() + .from(dnsRecords) + .where(eq(dnsRecords.domainId, domainId)); + + return records; +} + +export type GetDNSRecordsResponse = Awaited>; + +registry.registerPath({ + method: "get", + path: "/org/{orgId}/domain/{domainId}/dns-records", + description: "Get all DNS records for a domain by domainId.", + tags: [OpenAPITags.Domain], + request: { + params: z.object({ + domainId: z.string(), + orgId: z.string() + }) + }, + responses: {} +}); + +export async function getDNSRecords( + req: Request, + res: Response, + next: NextFunction +): Promise { + try { + const parsedParams = getDNSRecordsSchema.safeParse(req.params); + if (!parsedParams.success) { + return next( + createHttpError( + HttpCode.BAD_REQUEST, + fromError(parsedParams.error).toString() + ) + ); + } + + const { domainId } = parsedParams.data; + + const records = await query(domainId); + + if (!records || records.length === 0) { + return next( + createHttpError( + HttpCode.NOT_FOUND, + "No DNS records found for this domain" + ) + ); + } + + return response(res, { + data: records, + success: true, + error: false, + message: "DNS records retrieved successfully", + status: HttpCode.OK + }); + } catch (error) { + logger.error(error); + return next( + createHttpError(HttpCode.INTERNAL_SERVER_ERROR, "An error occurred") + ); + } +} \ No newline at end of file diff --git a/server/routers/domain/index.ts b/server/routers/domain/index.ts index e131e6a4..0bfedb41 100644 --- a/server/routers/domain/index.ts +++ b/server/routers/domain/index.ts @@ -2,4 +2,5 @@ export * from "./listDomains"; export * from "./createOrgDomain"; export * from "./deleteOrgDomain"; export * from "./restartOrgDomain"; -export * from "./getDomain"; \ No newline at end of file +export * from "./getDomain"; +export * from "./getDNSRecords"; \ No newline at end of file diff --git a/server/routers/external.ts b/server/routers/external.ts index cadbbad7..c00f1e9f 100644 --- a/server/routers/external.ts +++ b/server/routers/external.ts @@ -309,6 +309,13 @@ authenticated.get( domain.getDomain ); +authenticated.get( + "/org/:orgId/domain/:domainId/dns-records", + verifyOrgAccess, + verifyUserHasAction(ActionsEnum.getDNSRecords), + domain.getDNSRecords +); + authenticated.get( "/org/:orgId/invitations", verifyOrgAccess,