Add DNS made easy stuff

This commit is contained in:
Fang-Pen Lin
2025-11-25 08:54:16 -08:00
parent dd0b0e4633
commit 551e9b4476
7 changed files with 90 additions and 7 deletions
@@ -4,6 +4,7 @@ import { getConfig } from "@app/lib/config/env";
import { request } from "@app/lib/config/request"; import { request } from "@app/lib/config/request";
import { crypto } from "@app/lib/crypto/cryptography"; import { crypto } from "@app/lib/crypto/cryptography";
import { BadRequestError } from "@app/lib/errors"; import { BadRequestError } from "@app/lib/errors";
import { logger } from "@app/lib/logger";
import { AppConnection } from "@app/services/app-connection/app-connection-enums"; import { AppConnection } from "@app/services/app-connection/app-connection-enums";
import { IntegrationUrls } from "@app/services/integration-auth/integration-list"; import { IntegrationUrls } from "@app/services/integration-auth/integration-list";
@@ -116,6 +117,7 @@ export const validateDNSMadeEasyConnectionCredentials = async (config: TDNSMadeE
message: `Failed to validate credentials: ${error.response?.data?.error?.[0] || error.message || "Unknown error"}` message: `Failed to validate credentials: ${error.response?.data?.error?.[0] || error.message || "Unknown error"}`
}); });
} }
logger.error(error, "Error validating DNS Made Easy connection credentials");
throw new BadRequestError({ throw new BadRequestError({
message: "Unable to validate connection: verify credentials" message: "Unable to validate connection: verify credentials"
}); });
@@ -1,4 +1,5 @@
export enum AcmeDnsProvider { export enum AcmeDnsProvider {
Route53 = "route53", Route53 = "route53",
Cloudflare = "cloudflare" Cloudflare = "cloudflare",
DNSMadeEasy = "dns-made-easy"
} }
@@ -44,6 +44,7 @@ import {
} from "./acme-certificate-authority-types"; } from "./acme-certificate-authority-types";
import { cloudflareDeleteTxtRecord, cloudflareInsertTxtRecord } from "./dns-providers/cloudflare"; import { cloudflareDeleteTxtRecord, cloudflareInsertTxtRecord } from "./dns-providers/cloudflare";
import { route53DeleteTxtRecord, route53InsertTxtRecord } from "./dns-providers/route54"; import { route53DeleteTxtRecord, route53InsertTxtRecord } from "./dns-providers/route54";
import { TDNSMadeEasyConnection } from "@app/services/app-connection/dns-made-easy/dns-made-easy-connection-types";
type TAcmeCertificateAuthorityFnsDeps = { type TAcmeCertificateAuthorityFnsDeps = {
appConnectionDAL: Pick<TAppConnectionDALFactory, "findById">; appConnectionDAL: Pick<TAppConnectionDALFactory, "findById">;
@@ -263,6 +264,15 @@ export const orderCertificate = async (
); );
break; break;
} }
case AcmeDnsProvider.DNSMadeEasy: {
await dnsMadeEasyInsertTxtRecord(
connection as TDNSMadeEasyConnection,
acmeCa.configuration.dnsProviderConfig.hostedZoneId,
recordName,
recordValue
);
break;
}
default: { default: {
throw new Error(`Unsupported DNS provider: ${acmeCa.configuration.dnsProviderConfig.provider as string}`); throw new Error(`Unsupported DNS provider: ${acmeCa.configuration.dnsProviderConfig.provider as string}`);
} }
@@ -291,6 +301,14 @@ export const orderCertificate = async (
); );
break; break;
} }
case AcmeDnsProvider.DNSMadeEasy: {
await dnsMadeEasyDeleteTxtRecord(
connection as TDNSMadeEasyConnection,
acmeCa.configuration.dnsProviderConfig.hostedZoneId,
recordName,
recordValue
);
}
default: { default: {
throw new Error(`Unsupported DNS provider: ${acmeCa.configuration.dnsProviderConfig.provider as string}`); throw new Error(`Unsupported DNS provider: ${acmeCa.configuration.dnsProviderConfig.provider as string}`);
} }
@@ -413,6 +431,12 @@ export const AcmeCertificateAuthorityFns = ({
}); });
} }
if (dnsProviderConfig.provider === AcmeDnsProvider.DNSMadeEasy && appConnection.app !== AppConnection.DNSMadeEasy) {
throw new BadRequestError({
message: `App connection with ID '${dnsAppConnectionId}' is not a DNS Made Easy connection`
});
}
// validates permission to connect // validates permission to connect
await appConnectionService.validateAppConnectionUsageById( await appConnectionService.validateAppConnectionUsageById(
appConnection.app as AppConnection, appConnection.app as AppConnection,
@@ -508,6 +532,15 @@ export const AcmeCertificateAuthorityFns = ({
}); });
} }
if (
dnsProviderConfig.provider === AcmeDnsProvider.DNSMadeEasy &&
appConnection.app !== AppConnection.DNSMadeEasy
) {
throw new BadRequestError({
message: `App connection with ID '${dnsAppConnectionId}' is not a DNS Made Easy connection`
});
}
const ca = await certificateAuthorityDAL.findById(id); const ca = await certificateAuthorityDAL.findById(id);
if (!ca) { if (!ca) {
@@ -106,8 +106,8 @@ export enum IntegrationUrls {
GITHUB_USER_INSTALLATIONS = "https://api.github.com/user/installations", GITHUB_USER_INSTALLATIONS = "https://api.github.com/user/installations",
CHEF_API_URL = "https://api.chef.io", CHEF_API_URL = "https://api.chef.io",
DNS_MADE_EASY_API_URL = "https://api.dnsmadeeasy.com/V2.0", DNS_MADE_EASY_API_URL = "https://api.dnsmadeeasy.com",
DNS_MADE_EASY_SANDBOX_API_URL = "https://api.sandbox.dnsmadeeasy.com/V2.0" DNS_MADE_EASY_SANDBOX_API_URL = "https://api.sandbox.dnsmadeeasy.com"
} }
export const getIntegrationOptions = async () => { export const getIntegrationOptions = async () => {
+4 -2
View File
@@ -16,12 +16,14 @@ export const caStatusToNameMap: { [K in CaStatus]: string } = {
export const ACME_DNS_PROVIDER_NAME_MAP: Record<AcmeDnsProvider, string> = { export const ACME_DNS_PROVIDER_NAME_MAP: Record<AcmeDnsProvider, string> = {
[AcmeDnsProvider.ROUTE53]: "Route53", [AcmeDnsProvider.ROUTE53]: "Route53",
[AcmeDnsProvider.Cloudflare]: "Cloudflare" [AcmeDnsProvider.Cloudflare]: "Cloudflare",
[AcmeDnsProvider.DNSMadeEasy]: "DNS Made Easy"
}; };
export const ACME_DNS_PROVIDER_APP_CONNECTION_MAP: Record<AcmeDnsProvider, AppConnection> = { export const ACME_DNS_PROVIDER_APP_CONNECTION_MAP: Record<AcmeDnsProvider, AppConnection> = {
[AcmeDnsProvider.ROUTE53]: AppConnection.AWS, [AcmeDnsProvider.ROUTE53]: AppConnection.AWS,
[AcmeDnsProvider.Cloudflare]: AppConnection.Cloudflare [AcmeDnsProvider.Cloudflare]: AppConnection.Cloudflare,
[AcmeDnsProvider.DNSMadeEasy]: AppConnection.DNSMadeEasy
}; };
export const CA_TYPE_CAPABILITIES_MAP: Record<CaType, CaCapability[]> = { export const CA_TYPE_CAPABILITIES_MAP: Record<CaType, CaCapability[]> = {
+2 -1
View File
@@ -21,7 +21,8 @@ export enum CaRenewalType {
export enum AcmeDnsProvider { export enum AcmeDnsProvider {
ROUTE53 = "route53", ROUTE53 = "route53",
Cloudflare = "cloudflare" Cloudflare = "cloudflare",
DNSMadeEasy = "dns-made-easy"
} }
export enum CaCapability { export enum CaCapability {
@@ -27,6 +27,10 @@ import {
TCloudflareZone, TCloudflareZone,
useCloudflareConnectionListZones useCloudflareConnectionListZones
} from "@app/hooks/api/appConnections/cloudflare"; } from "@app/hooks/api/appConnections/cloudflare";
import {
TDNSMadeEasyZone,
useDNSMadeEasyConnectionListZones
} from "@app/hooks/api/appConnections/dns-made-easy";
import { AppConnection } from "@app/hooks/api/appConnections/enums"; import { AppConnection } from "@app/hooks/api/appConnections/enums";
import { import {
AcmeDnsProvider, AcmeDnsProvider,
@@ -211,6 +215,11 @@ export const ExternalCaModal = ({ popUp, handlePopUpToggle }: Props) => {
enabled: caType === CaType.ACME enabled: caType === CaType.ACME
}); });
const { data: availableDNSMadeEasyConnections, isPending: isDNSMadeEasyPending } =
useListAvailableAppConnections(AppConnection.DNSMadeEasy, currentProject.id, {
enabled: caType === CaType.ACME
});
const { data: availableAzureConnections, isPending: isAzurePending } = const { data: availableAzureConnections, isPending: isAzurePending } =
useListAvailableAppConnections(AppConnection.AzureADCS, currentProject.id, { useListAvailableAppConnections(AppConnection.AzureADCS, currentProject.id, {
enabled: caType === CaType.AZURE_AD_CS enabled: caType === CaType.AZURE_AD_CS
@@ -225,11 +234,15 @@ export const ExternalCaModal = ({ popUp, handlePopUpToggle }: Props) => {
caType, caType,
availableRoute53Connections, availableRoute53Connections,
availableCloudflareConnections, availableCloudflareConnections,
availableDNSMadeEasyConnections,
availableAzureConnections availableAzureConnections
]); ]);
const isPending = const isPending =
isRoute53Pending || isCloudflarePending || (isAzurePending && caType === CaType.AZURE_AD_CS); isRoute53Pending ||
isCloudflarePending ||
isDNSMadeEasyPending ||
(isAzurePending && caType === CaType.AZURE_AD_CS);
const dnsAppConnection = const dnsAppConnection =
caType === CaType.ACME && configuration && "dnsAppConnection" in configuration caType === CaType.ACME && configuration && "dnsAppConnection" in configuration
@@ -241,6 +254,11 @@ export const ExternalCaModal = ({ popUp, handlePopUpToggle }: Props) => {
enabled: dnsProvider === AcmeDnsProvider.Cloudflare && !!dnsAppConnection.id enabled: dnsProvider === AcmeDnsProvider.Cloudflare && !!dnsAppConnection.id
}); });
const { data: dnsMadeEasyZones = [], isPending: isDNSMadeEasyZonesPending } =
useDNSMadeEasyConnectionListZones(dnsAppConnection.id, {
enabled: dnsProvider === AcmeDnsProvider.DNSMadeEasy && !!dnsAppConnection.id
});
// Populate form with CA data when editing // Populate form with CA data when editing
useEffect(() => { useEffect(() => {
if (ca && !isCaLoading) { if (ca && !isCaLoading) {
@@ -500,6 +518,32 @@ export const ExternalCaModal = ({ popUp, handlePopUpToggle }: Props) => {
)} )}
/> />
)} )}
{dnsProvider === AcmeDnsProvider.DNSMadeEasy && (
<Controller
name="configuration.dnsProviderConfig.hostedZoneId"
control={control}
render={({ field: { value, onChange }, fieldState: { error } }) => (
<FormControl
errorText={error?.message}
isError={Boolean(error?.message)}
label="Zone"
>
<FilterableSelect
isLoading={isDNSMadeEasyZonesPending && !!dnsAppConnection.id}
isDisabled={!dnsAppConnection.id}
value={dnsMadeEasyZones.find((zone) => zone.id === value)}
onChange={(option) => {
onChange((option as SingleValue<TDNSMadeEasyZone>)?.id ?? null);
}}
options={dnsMadeEasyZones}
placeholder="Select a zone..."
getOptionLabel={(option) => option.name}
getOptionValue={(option) => option.id}
/>
</FormControl>
)}
/>
)}
<Controller <Controller
control={control} control={control}
defaultValue="" defaultValue=""