From 2a4269e68f6099e8e9606b880d6f9d14f13ffa41 Mon Sep 17 00:00:00 2001 From: Fu Diwei Date: Wed, 5 Nov 2025 23:04:53 +0800 Subject: [PATCH] feat(provider): new acme dns-01 provider: dnsmadeeasy --- .../certapply/applicators/sp_dnsmadeeasy.go | 30 ++++++++ internal/domain/access.go | 5 ++ internal/domain/provider.go | 2 +- .../providers/dnsmadeeasy/dnsmadeeasy.go | 40 +++++++++++ ui/public/imgs/providers/dnsmadeeasy.png | Bin 0 -> 4660 bytes .../forms/AccessConfigFieldsProvider.tsx | 2 + .../AccessConfigFieldsProviderDNSMadeEasy.tsx | 64 ++++++++++++++++++ ui/src/domain/provider.ts | 2 + ui/src/i18n/locales/en/nls.access.json | 6 ++ ui/src/i18n/locales/zh/nls.access.json | 6 ++ 10 files changed, 156 insertions(+), 1 deletion(-) create mode 100644 internal/certapply/applicators/sp_dnsmadeeasy.go create mode 100644 pkg/core/ssl-applicator/acme-dns01/providers/dnsmadeeasy/dnsmadeeasy.go create mode 100644 ui/public/imgs/providers/dnsmadeeasy.png create mode 100644 ui/src/components/access/forms/AccessConfigFieldsProviderDNSMadeEasy.tsx diff --git a/internal/certapply/applicators/sp_dnsmadeeasy.go b/internal/certapply/applicators/sp_dnsmadeeasy.go new file mode 100644 index 000000000..32919f2d4 --- /dev/null +++ b/internal/certapply/applicators/sp_dnsmadeeasy.go @@ -0,0 +1,30 @@ +package applicators + +import ( + "fmt" + + "github.com/go-acme/lego/v4/challenge" + + "github.com/certimate-go/certimate/internal/domain" + "github.com/certimate-go/certimate/pkg/core/ssl-applicator/acme-dns01/providers/dnsmadeeasy" + xmaps "github.com/certimate-go/certimate/pkg/utils/maps" +) + +func init() { + if err := ACMEDns01Registries.Register(domain.ACMEDns01ProviderTypeDNSMadeEasy, func(options *ProviderFactoryOptions) (challenge.Provider, error) { + credentials := domain.AccessConfigForDNSMadeEasy{} + if err := xmaps.Populate(options.ProviderAccessConfig, &credentials); err != nil { + return nil, fmt.Errorf("failed to populate provider access config: %w", err) + } + + provider, err := dnsmadeeasy.NewChallengeProvider(&dnsmadeeasy.ChallengeProviderConfig{ + ApiKey: credentials.ApiKey, + ApiSecret: credentials.ApiSecret, + DnsPropagationTimeout: options.DnsPropagationTimeout, + DnsTTL: options.DnsTTL, + }) + return provider, err + }); err != nil { + panic(err) + } +} diff --git a/internal/domain/access.go b/internal/domain/access.go index 0a3cb9985..43d6d952a 100644 --- a/internal/domain/access.go +++ b/internal/domain/access.go @@ -176,6 +176,11 @@ type AccessConfigForDNSLA struct { ApiSecret string `json:"apiSecret"` } +type AccessConfigForDNSMadeEasy struct { + ApiKey string `json:"apiKey"` + ApiSecret string `json:"apiSecret"` +} + type AccessConfigForDogeCloud struct { AccessKey string `json:"accessKey"` SecretKey string `json:"secretKey"` diff --git a/internal/domain/provider.go b/internal/domain/provider.go index c8e215ab5..0076e2965 100644 --- a/internal/domain/provider.go +++ b/internal/domain/provider.go @@ -41,7 +41,7 @@ const ( AccessProviderTypeDingTalkBot = AccessProviderType("dingtalkbot") AccessProviderTypeDiscordBot = AccessProviderType("discordbot") AccessProviderTypeDNSLA = AccessProviderType("dnsla") - AccessProviderTypeDNSMadeEasy = AccessProviderType("dnsmadeeasy") // DNS Made Easy(预留) + AccessProviderTypeDNSMadeEasy = AccessProviderType("dnsmadeeasy") AccessProviderTypeDogeCloud = AccessProviderType("dogecloud") AccessProviderTypeDuckDNS = AccessProviderType("duckdns") AccessProviderTypeDynu = AccessProviderType("dynu") // Dynu(预留) diff --git a/pkg/core/ssl-applicator/acme-dns01/providers/dnsmadeeasy/dnsmadeeasy.go b/pkg/core/ssl-applicator/acme-dns01/providers/dnsmadeeasy/dnsmadeeasy.go new file mode 100644 index 000000000..a8bef47ce --- /dev/null +++ b/pkg/core/ssl-applicator/acme-dns01/providers/dnsmadeeasy/dnsmadeeasy.go @@ -0,0 +1,40 @@ +package dnsmadeeasy + +import ( + "errors" + "time" + + "github.com/go-acme/lego/v4/providers/dns/dnsmadeeasy" + + "github.com/certimate-go/certimate/pkg/core" +) + +type ChallengeProviderConfig struct { + ApiKey string `json:"apiKey"` + ApiSecret string `json:"apiSecret"` + DnsPropagationTimeout int32 `json:"dnsPropagationTimeout,omitempty"` + DnsTTL int32 `json:"dnsTTL,omitempty"` +} + +func NewChallengeProvider(config *ChallengeProviderConfig) (core.ACMEChallenger, error) { + if config == nil { + return nil, errors.New("the configuration of the acme challenge provider is nil") + } + + providerConfig := dnsmadeeasy.NewDefaultConfig() + providerConfig.APIKey = config.ApiKey + providerConfig.APISecret = config.ApiSecret + if config.DnsPropagationTimeout != 0 { + providerConfig.PropagationTimeout = time.Duration(config.DnsPropagationTimeout) * time.Second + } + if config.DnsTTL != 0 { + providerConfig.TTL = int(config.DnsTTL) + } + + provider, err := dnsmadeeasy.NewDNSProviderConfig(providerConfig) + if err != nil { + return nil, err + } + + return provider, nil +} diff --git a/ui/public/imgs/providers/dnsmadeeasy.png b/ui/public/imgs/providers/dnsmadeeasy.png new file mode 100644 index 0000000000000000000000000000000000000000..b7f15edb16fe02f5fb9865509895ca736ad16129 GIT binary patch literal 4660 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 { + const { i18n, t } = useTranslation(); + + const { parentNamePath } = useFormNestedFieldsContext(); + const formSchema = z.object({ + [parentNamePath]: getSchema({ i18n }), + }); + const formRule = createSchemaFieldRule(formSchema); + const initialValues = getInitialValues(); + + return ( + <> + } + > + + + + } + > + + + + ); +}; + +const getInitialValues = (): Nullish>> => { + return { + apiKey: "", + apiSecret: "", + }; +}; + +const getSchema = ({ i18n = getI18n() }: { i18n: ReturnType }) => { + const { t } = i18n; + + return z.object({ + apiKey: z.string().nonempty(t("access.form.dnsmadeeasy_api_key.placeholder")), + apiSecret: z.string().nonempty(t("access.form.dnsmadeeasy_api_secret.placeholder")), + }); +}; + +const _default = Object.assign(AccessConfigFormFieldsProviderDNSMadeEasy, { + getInitialValues, + getSchema, +}); + +export default _default; diff --git a/ui/src/domain/provider.ts b/ui/src/domain/provider.ts index 5a3a38623..b068f13c8 100644 --- a/ui/src/domain/provider.ts +++ b/ui/src/domain/provider.ts @@ -183,6 +183,7 @@ export const accessProvidersMap: Maphttps://www.dns.la/docs/ApiDoc", + "access.form.dnsmadeeasy_api_key.label": "DNS Made Easy API key", + "access.form.dnsmadeeasy_api_key.placeholder": "Please enter DNS Made Easy API key", + "access.form.dnsmadeeasy_api_key.tooltip": "For more information, see https://api-docs.dnsmadeeasy.com/#Authentication", + "access.form.dnsmadeeasy_api_secret.label": "DNS Made Easy API secret", + "access.form.dnsmadeeasy_api_secret.placeholder": "Please enter DNS Made Easy API secret", + "access.form.dnsmadeeasy_api_secret.tooltip": "For more information, see https://api-docs.dnsmadeeasy.com/#Authentication", "access.form.dogecloud_access_key.label": "Doge Cloud AccessKey", "access.form.dogecloud_access_key.placeholder": "Please enter Doge Cloud AccessKey", "access.form.dogecloud_access_key.tooltip": "For more information, see https://console.dogecloud.com/", diff --git a/ui/src/i18n/locales/zh/nls.access.json b/ui/src/i18n/locales/zh/nls.access.json index ca95e968b..babc6115f 100644 --- a/ui/src/i18n/locales/zh/nls.access.json +++ b/ui/src/i18n/locales/zh/nls.access.json @@ -217,6 +217,12 @@ "access.form.discordbot_channel_id.placeholder": "请输入默认的 Discord 频道 ID", "access.form.discordbot_channel_id.help": "提示:可在工作流中覆盖此设置。", "access.form.discordbot_channel_id.tooltip": "这是什么?请参阅 https://support.discord.com/hc/en-us/articles/206346498-Where-can-I-find-my-User-Server-Message-ID", + "access.form.dnsmadeeasy_api_key.label": "DNS Made Easy API Key", + "access.form.dnsmadeeasy_api_key.placeholder": "请输入 DNS Made Easy API Key", + "access.form.dnsmadeeasy_api_key.tooltip": "这是什么?请参阅 https://api-docs.dnsmadeeasy.com/#Authentication", + "access.form.dnsmadeeasy_api_secret.label": "DNS Made Easy API Secret", + "access.form.dnsmadeeasy_api_secret.placeholder": "请输入 DNS Made Easy API Secret", + "access.form.dnsmadeeasy_api_secret.tooltip": "这是什么?请参阅 https://api-docs.dnsmadeeasy.com/#Authentication", "access.form.dnsla_api_id.label": "DNS.LA API ID", "access.form.dnsla_api_id.placeholder": "请输入 DNS.LA API ID", "access.form.dnsla_api_id.tooltip": "这是什么?请参阅 https://www.dns.la/docs/ApiDoc",