Add scaffolding for DNS made easy

This commit is contained in:
Fang-Pen Lin
2025-11-19 17:37:26 -08:00
parent 0d62ef6fb5
commit 126c3be606
11 changed files with 293 additions and 0 deletions

View File

@@ -0,0 +1,52 @@
import z from "zod";
import { readLimit } from "@app/server/config/rateLimiter";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
import { AppConnection } from "@app/services/app-connection/app-connection-enums";
import {
CreateDNSMadeEasyConnectionSchema,
SanitizedDNSMadeEasyConnectionSchema,
UpdateDNSMadeEasyConnectionSchema
} from "@app/services/app-connection/dns-made-easy/dns-made-easy-connection-schema";
import { AuthMode } from "@app/services/auth/auth-type";
import { registerAppConnectionEndpoints } from "./app-connection-endpoints";
export const registerDNSMadeEasyConnectionRouter = async (server: FastifyZodProvider) => {
registerAppConnectionEndpoints({
app: AppConnection.DNSMadeEasy,
server,
sanitizedResponseSchema: SanitizedDNSMadeEasyConnectionSchema,
createSchema: CreateDNSMadeEasyConnectionSchema,
updateSchema: UpdateDNSMadeEasyConnectionSchema
});
// The below endpoints are not exposed and for Infisical App use
server.route({
method: "GET",
url: `/:connectionId/dns-made-easy-zones`,
config: {
rateLimit: readLimit
},
schema: {
params: z.object({
connectionId: z.string().uuid()
}),
response: {
200: z
.object({
id: z.string(),
name: z.string()
})
.array()
}
},
onRequest: verifyAuth([AuthMode.JWT]),
handler: async (req) => {
const { connectionId } = req.params;
const zones = await server.services.appConnection.dnsMadeEasy.listZones(connectionId, req.permission);
return zones;
}
});
};

View File

@@ -16,6 +16,7 @@ import { registerCamundaConnectionRouter } from "./camunda-connection-router";
import { registerChecklyConnectionRouter } from "./checkly-connection-router";
import { registerCloudflareConnectionRouter } from "./cloudflare-connection-router";
import { registerDatabricksConnectionRouter } from "./databricks-connection-router";
import { registerDNSMadeEasyConnectionRouter } from "./dns-made-easy-connection-router";
import { registerDigitalOceanConnectionRouter } from "./digital-ocean-connection-router";
import { registerFlyioConnectionRouter } from "./flyio-connection-router";
import { registerGcpConnectionRouter } from "./gcp-connection-router";
@@ -78,6 +79,7 @@ export const APP_CONNECTION_REGISTER_ROUTER_MAP: Record<AppConnection, (server:
[AppConnection.Flyio]: registerFlyioConnectionRouter,
[AppConnection.GitLab]: registerGitLabConnectionRouter,
[AppConnection.Cloudflare]: registerCloudflareConnectionRouter,
[AppConnection.DNSMadeEasy]: registerDNSMadeEasyConnectionRouter,
[AppConnection.Bitbucket]: registerBitbucketConnectionRouter,
[AppConnection.Zabbix]: registerZabbixConnectionRouter,
[AppConnection.Railway]: registerRailwayConnectionRouter,

View File

@@ -29,6 +29,7 @@ export enum AppConnection {
Flyio = "flyio",
GitLab = "gitlab",
Cloudflare = "cloudflare",
DNSMadeEasy = "dns-made-easy",
Zabbix = "zabbix",
Railway = "railway",
Bitbucket = "bitbucket",

View File

@@ -78,6 +78,11 @@ import {
getCloudflareConnectionListItem,
validateCloudflareConnectionCredentials
} from "./cloudflare/cloudflare-connection-fns";
import { DNSMadeEasyConnectionMethod } from "./dns-made-easy/dns-made-easy-connection-enum";
import {
getDNSMadeEasyConnectionListItem,
validateDNSMadeEasyConnectionCredentials
} from "./dns-made-easy/dns-made-easy-connection-fns";
import {
DatabricksConnectionMethod,
getDatabricksConnectionListItem,
@@ -207,6 +212,7 @@ export const listAppConnectionOptions = (projectType?: ProjectType) => {
getFlyioConnectionListItem(),
getGitLabConnectionListItem(),
getCloudflareConnectionListItem(),
getDNSMadeEasyConnectionListItem(),
getZabbixConnectionListItem(),
getRailwayConnectionListItem(),
getBitbucketConnectionListItem(),
@@ -339,6 +345,7 @@ export const validateAppConnectionCredentials = async (
[AppConnection.Flyio]: validateFlyioConnectionCredentials as TAppConnectionCredentialsValidator,
[AppConnection.GitLab]: validateGitLabConnectionCredentials as TAppConnectionCredentialsValidator,
[AppConnection.Cloudflare]: validateCloudflareConnectionCredentials as TAppConnectionCredentialsValidator,
[AppConnection.DNSMadeEasy]: validateDNSMadeEasyConnectionCredentials as TAppConnectionCredentialsValidator,
[AppConnection.Zabbix]: validateZabbixConnectionCredentials as TAppConnectionCredentialsValidator,
[AppConnection.Railway]: validateRailwayConnectionCredentials as TAppConnectionCredentialsValidator,
[AppConnection.Bitbucket]: validateBitbucketConnectionCredentials as TAppConnectionCredentialsValidator,
@@ -483,6 +490,7 @@ export const TRANSITION_CONNECTION_CREDENTIALS_TO_PLATFORM: Record<
[AppConnection.Flyio]: platformManagedCredentialsNotSupported,
[AppConnection.GitLab]: platformManagedCredentialsNotSupported,
[AppConnection.Cloudflare]: platformManagedCredentialsNotSupported,
[AppConnection.DNSMadeEasy]: platformManagedCredentialsNotSupported,
[AppConnection.Zabbix]: platformManagedCredentialsNotSupported,
[AppConnection.Railway]: platformManagedCredentialsNotSupported,
[AppConnection.Bitbucket]: platformManagedCredentialsNotSupported,

View File

@@ -32,6 +32,7 @@ export const APP_CONNECTION_NAME_MAP: Record<AppConnection, string> = {
[AppConnection.Flyio]: "Fly.io",
[AppConnection.GitLab]: "GitLab",
[AppConnection.Cloudflare]: "Cloudflare",
[AppConnection.DNSMadeEasy]: "DNS Made Easy",
[AppConnection.Zabbix]: "Zabbix",
[AppConnection.Railway]: "Railway",
[AppConnection.Bitbucket]: "Bitbucket",
@@ -77,6 +78,7 @@ export const APP_CONNECTION_PLAN_MAP: Record<AppConnection, AppConnectionPlanTyp
[AppConnection.Flyio]: AppConnectionPlanType.Regular,
[AppConnection.GitLab]: AppConnectionPlanType.Regular,
[AppConnection.Cloudflare]: AppConnectionPlanType.Regular,
[AppConnection.DNSMadeEasy]: AppConnectionPlanType.Regular,
[AppConnection.Zabbix]: AppConnectionPlanType.Regular,
[AppConnection.Railway]: AppConnectionPlanType.Regular,
[AppConnection.Bitbucket]: AppConnectionPlanType.Regular,

View File

@@ -72,6 +72,8 @@ import { checklyConnectionService } from "./checkly/checkly-connection-service";
import { ValidateCloudflareConnectionCredentialsSchema } from "./cloudflare/cloudflare-connection-schema";
import { cloudflareConnectionService } from "./cloudflare/cloudflare-connection-service";
import { ValidateDatabricksConnectionCredentialsSchema } from "./databricks";
import { ValidateDNSMadeEasyConnectionCredentialsSchema } from "./dns-made-easy/dns-made-easy-connection-schema";
import { dnsMadeEasyConnectionService } from "./dns-made-easy/dns-made-easy-connection-service";
import { databricksConnectionService } from "./databricks/databricks-connection-service";
import { ValidateDigitalOceanConnectionCredentialsSchema } from "./digital-ocean";
import { digitalOceanAppPlatformConnectionService } from "./digital-ocean/digital-ocean-connection-service";
@@ -167,6 +169,7 @@ const VALIDATE_APP_CONNECTION_CREDENTIALS_MAP: Record<AppConnection, TValidateAp
[AppConnection.Flyio]: ValidateFlyioConnectionCredentialsSchema,
[AppConnection.GitLab]: ValidateGitLabConnectionCredentialsSchema,
[AppConnection.Cloudflare]: ValidateCloudflareConnectionCredentialsSchema,
[AppConnection.DNSMadeEasy]: ValidateDNSMadeEasyConnectionCredentialsSchema,
[AppConnection.Zabbix]: ValidateZabbixConnectionCredentialsSchema,
[AppConnection.Railway]: ValidateRailwayConnectionCredentialsSchema,
[AppConnection.Bitbucket]: ValidateBitbucketConnectionCredentialsSchema,
@@ -875,6 +878,7 @@ export const appConnectionServiceFactory = ({
flyio: flyioConnectionService(connectAppConnectionById),
gitlab: gitlabConnectionService(connectAppConnectionById, appConnectionDAL, kmsService),
cloudflare: cloudflareConnectionService(connectAppConnectionById),
dnsMadeEasy: dnsMadeEasyConnectionService(connectAppConnectionById),
zabbix: zabbixConnectionService(connectAppConnectionById),
railway: railwayConnectionService(connectAppConnectionById),
bitbucket: bitbucketConnectionService(connectAppConnectionById),

View File

@@ -0,0 +1,3 @@
export enum DNSMadeEasyConnectionMethod {
APIKey = "api-key"
}

View File

@@ -0,0 +1,101 @@
import { AxiosError } from "axios";
import { request } from "@app/lib/config/request";
import { BadRequestError } from "@app/lib/errors";
import { AppConnection } from "@app/services/app-connection/app-connection-enums";
import { IntegrationUrls } from "@app/services/integration-auth/integration-list";
import { DNSMadeEasyConnectionMethod } from "./dns-made-easy-connection-enum";
import {
TDNSMadeEasyConnection,
TDNSMadeEasyConnectionConfig,
TDNSMadeEasyZone
} from "./dns-made-easy-connection-types";
export const getDNSMadeEasyConnectionListItem = () => {
return {
name: "DNS Made Easy" as const,
app: AppConnection.DNSMadeEasy as const,
methods: Object.values(DNSMadeEasyConnectionMethod) as [DNSMadeEasyConnectionMethod.APIKey]
};
};
export const listDNSMadeEasyZones = async (appConnection: TDNSMadeEasyConnection): Promise<TDNSMadeEasyZone[]> => {
// TODO: Implement DNS Made Easy zones listing
// This should call the DNS Made Easy API to list all zones/domains
// Example API endpoint: GET https://api.dnsmadeeasy.com/V2.0/dns/managed
// Authentication: Use API key and secret from appConnection.credentials
// Return format: Array of { id: string, name: string }
if (appConnection.method !== DNSMadeEasyConnectionMethod.APIKey) {
throw new Error("Unsupported DNS Made Easy connection method");
}
const {
credentials: { apiKey, apiSecret }
} = appConnection;
// TODO: Make API request to DNS Made Easy
// const { data } = await request.get<{ data: { id: number; name: string }[] }>(
// `${IntegrationUrls.DNS_MADE_EASY_API_URL}/V2.0/dns/managed`,
// {
// headers: {
// "x-dnsme-apiKey": apiKey,
// "x-dnsme-hmac": generateHMAC(apiSecret, ...),
// "x-dnsme-requestDate": requestDate,
// Accept: "application/json"
// }
// }
// );
// TODO: Transform response data to match TDNSMadeEasyZone format
// return data.data.map((zone) => ({
// name: zone.name,
// id: zone.id.toString()
// }));
throw new Error("Not implemented: listDNSMadeEasyZones");
};
export const validateDNSMadeEasyConnectionCredentials = async (config: TDNSMadeEasyConnectionConfig) => {
// TODO: Implement DNS Made Easy credentials validation
// This should call the DNS Made Easy API to validate the API key and secret
// Example API endpoint: GET https://api.dnsmadeeasy.com/V2.0/account
// Authentication: Use API key and secret from config.credentials
if (config.method !== DNSMadeEasyConnectionMethod.APIKey) {
throw new Error("Unsupported DNS Made Easy connection method");
}
const { apiKey, apiSecret } = config.credentials;
try {
// TODO: Make API request to validate credentials
// const resp = await request.get(`${IntegrationUrls.DNS_MADE_EASY_API_URL}/V2.0/account`, {
// headers: {
// "x-dnsme-apiKey": apiKey,
// "x-dnsme-hmac": generateHMAC(apiSecret, ...),
// "x-dnsme-requestDate": requestDate,
// Accept: "application/json"
// }
// });
// TODO: Validate response
// if (resp.data === null || !resp.data.id) {
// throw new BadRequestError({
// message: "Unable to validate connection: Invalid API credentials provided."
// });
// }
} catch (error: unknown) {
if (error instanceof AxiosError) {
throw new BadRequestError({
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
message: `Failed to validate credentials: ${error.response?.data?.error?.[0] || error.message || "Unknown error"}`
});
}
throw new BadRequestError({
message: "Unable to validate connection: verify credentials"
});
}
return config.credentials;
};

View File

@@ -0,0 +1,64 @@
import z from "zod";
import { AppConnections } from "@app/lib/api-docs";
import { AppConnection } from "@app/services/app-connection/app-connection-enums";
import {
BaseAppConnectionSchema,
GenericCreateAppConnectionFieldsSchema,
GenericUpdateAppConnectionFieldsSchema
} from "@app/services/app-connection/app-connection-schemas";
import { APP_CONNECTION_NAME_MAP } from "../app-connection-maps";
import { DNSMadeEasyConnectionMethod } from "./dns-made-easy-connection-enum";
export const DNSMadeEasyConnectionApiKeyCredentialsSchema = z.object({
apiKey: z.string().trim().min(1, "API key required").max(256, "API key cannot exceed 256 characters"),
apiSecret: z.string().trim().min(1, "API secret required").max(256, "API secret cannot exceed 256 characters")
});
const BaseDNSMadeEasyConnectionSchema = BaseAppConnectionSchema.extend({
app: z.literal(AppConnection.DNSMadeEasy)
});
export const DNSMadeEasyConnectionSchema = BaseDNSMadeEasyConnectionSchema.extend({
method: z.literal(DNSMadeEasyConnectionMethod.APIKey),
credentials: DNSMadeEasyConnectionApiKeyCredentialsSchema
});
export const SanitizedDNSMadeEasyConnectionSchema = z.discriminatedUnion("method", [
BaseDNSMadeEasyConnectionSchema.extend({
method: z.literal(DNSMadeEasyConnectionMethod.APIKey),
credentials: DNSMadeEasyConnectionApiKeyCredentialsSchema.pick({ apiKey: true })
}).describe(JSON.stringify({ title: `${APP_CONNECTION_NAME_MAP[AppConnection.DNSMadeEasy]} (API Key)` }))
]);
export const ValidateDNSMadeEasyConnectionCredentialsSchema = z.discriminatedUnion("method", [
z.object({
method: z
.literal(DNSMadeEasyConnectionMethod.APIKey)
.describe(AppConnections.CREATE(AppConnection.DNSMadeEasy).method),
credentials: DNSMadeEasyConnectionApiKeyCredentialsSchema.describe(
AppConnections.CREATE(AppConnection.DNSMadeEasy).credentials
)
})
]);
export const CreateDNSMadeEasyConnectionSchema = ValidateDNSMadeEasyConnectionCredentialsSchema.and(
GenericCreateAppConnectionFieldsSchema(AppConnection.DNSMadeEasy)
);
export const UpdateDNSMadeEasyConnectionSchema = z
.object({
credentials: DNSMadeEasyConnectionApiKeyCredentialsSchema.optional().describe(
AppConnections.UPDATE(AppConnection.DNSMadeEasy).credentials
)
})
.and(GenericUpdateAppConnectionFieldsSchema(AppConnection.DNSMadeEasy));
export const DNSMadeEasyConnectionListItemSchema = z
.object({
name: z.literal("DNS Made Easy"),
app: z.literal(AppConnection.DNSMadeEasy),
methods: z.nativeEnum(DNSMadeEasyConnectionMethod).array()
})
.describe(JSON.stringify({ title: APP_CONNECTION_NAME_MAP[AppConnection.DNSMadeEasy] }));

View File

@@ -0,0 +1,32 @@
import { logger } from "@app/lib/logger";
import { OrgServiceActor } from "@app/lib/types";
import { AppConnection } from "../app-connection-enums";
import { listDNSMadeEasyZones } from "./dns-made-easy-connection-fns";
import { TDNSMadeEasyConnection } from "./dns-made-easy-connection-types";
type TGetAppConnectionFunc = (
app: AppConnection,
connectionId: string,
actor: OrgServiceActor
) => Promise<TDNSMadeEasyConnection>;
export const dnsMadeEasyConnectionService = (getAppConnection: TGetAppConnectionFunc) => {
const listZones = async (connectionId: string, actor: OrgServiceActor) => {
const appConnection = await getAppConnection(AppConnection.DNSMadeEasy, connectionId, actor);
try {
const zones = await listDNSMadeEasyZones(appConnection);
return zones;
} catch (error) {
logger.error(
error,
`Failed to list DNS Made Easy zones for DNS Made Easy connection [connectionId=${connectionId}]`
);
return [];
}
};
return {
listZones
};
};

View File

@@ -0,0 +1,24 @@
import z from "zod";
import { DiscriminativePick } from "@app/lib/types";
import { AppConnection } from "../app-connection-enums";
import { DNSMadeEasyConnectionSchema, CreateDNSMadeEasyConnectionSchema } from "./dns-made-easy-connection-schema";
export type TDNSMadeEasyConnection = z.infer<typeof DNSMadeEasyConnectionSchema>;
export type TDNSMadeEasyConnectionInput = z.infer<typeof CreateDNSMadeEasyConnectionSchema> & {
app: AppConnection.DNSMadeEasy;
};
export type TDNSMadeEasyConnectionConfig = DiscriminativePick<
TDNSMadeEasyConnectionInput,
"method" | "app" | "credentials"
> & {
orgId: string;
};
export type TDNSMadeEasyZone = {
id: string;
name: string;
};