From f4ea0a071f0743828b6ab083c09b5e3a104a7faf Mon Sep 17 00:00:00 2001 From: Fu Diwei Date: Wed, 22 Oct 2025 22:25:13 +0800 Subject: [PATCH] feat(provider): new acme dns-01 provider: bookmyname --- .../certapply/applicators/sp_bookmyname.go | 30 +++++++++ internal/domain/access.go | 11 +++- internal/domain/provider.go | 3 +- .../providers/bookmyname/bookmyname.go | 40 +++++++++++ .../acme-dns01/providers/hetzner/hetzner.go | 4 +- ui/public/imgs/providers/bookmyname.png | Bin 0 -> 522 bytes ui/src/components/access/AccessForm.tsx | 4 ++ .../AccessConfigFieldsProviderBookMyName.tsx | 62 ++++++++++++++++++ ui/src/domain/provider.ts | 4 ++ ui/src/i18n/locales/en/nls.access.json | 4 ++ ui/src/i18n/locales/en/nls.provider.json | 1 + ui/src/i18n/locales/zh/nls.access.json | 6 +- ui/src/i18n/locales/zh/nls.provider.json | 1 + 13 files changed, 163 insertions(+), 7 deletions(-) create mode 100644 internal/certapply/applicators/sp_bookmyname.go create mode 100644 pkg/core/ssl-applicator/acme-dns01/providers/bookmyname/bookmyname.go create mode 100644 ui/public/imgs/providers/bookmyname.png create mode 100644 ui/src/components/access/forms/AccessConfigFieldsProviderBookMyName.tsx diff --git a/internal/certapply/applicators/sp_bookmyname.go b/internal/certapply/applicators/sp_bookmyname.go new file mode 100644 index 000000000..90c9c3a28 --- /dev/null +++ b/internal/certapply/applicators/sp_bookmyname.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/bookmyname" + xmaps "github.com/certimate-go/certimate/pkg/utils/maps" +) + +func init() { + if err := ACMEDns01Registries.Register(domain.ACMEDns01ProviderTypeBookMyName, func(options *ProviderFactoryOptions) (challenge.Provider, error) { + credentials := domain.AccessConfigForBookMyName{} + if err := xmaps.Populate(options.ProviderAccessConfig, &credentials); err != nil { + return nil, fmt.Errorf("failed to populate provider access config: %w", err) + } + + provider, err := bookmyname.NewChallengeProvider(&bookmyname.ChallengeProviderConfig{ + Username: credentials.Username, + Password: credentials.Password, + 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 9b3f10bcb..dde8954cf 100644 --- a/internal/domain/access.go +++ b/internal/domain/access.go @@ -93,15 +93,20 @@ type AccessConfigForBaotaWAF struct { AllowInsecureConnections bool `json:"allowInsecureConnections,omitempty"` } -type AccessConfigForBytePlus struct { - AccessKey string `json:"accessKey"` - SecretKey string `json:"secretKey"` +type AccessConfigForBookMyName struct { + Username string `json:"username"` + Password string `json:"password"` } type AccessConfigForBunny struct { ApiKey string `json:"apiKey"` } +type AccessConfigForBytePlus struct { + AccessKey string `json:"accessKey"` + SecretKey string `json:"secretKey"` +} + type AccessConfigForCacheFly struct { ApiToken string `json:"apiToken"` } diff --git a/internal/domain/provider.go b/internal/domain/provider.go index c51b9430c..bb970cd26 100644 --- a/internal/domain/provider.go +++ b/internal/domain/provider.go @@ -23,7 +23,7 @@ const ( AccessProviderTypeBaishan = AccessProviderType("baishan") AccessProviderTypeBaotaPanel = AccessProviderType("baotapanel") AccessProviderTypeBaotaWAF = AccessProviderType("baotawaf") - AccessProviderTypeBookMyName = AccessProviderType("bookmyname") // BookMyName(预留) + AccessProviderTypeBookMyName = AccessProviderType("bookmyname") AccessProviderTypeBunny = AccessProviderType("bunny") AccessProviderTypeBytePlus = AccessProviderType("byteplus") AccessProviderTypeCacheFly = AccessProviderType("cachefly") @@ -144,6 +144,7 @@ const ( ACMEDns01ProviderTypeAzureDNS = ACMEDns01ProviderType(AccessProviderTypeAzure + "-dns") ACMEDns01ProviderTypeBaiduCloud = ACMEDns01ProviderType(AccessProviderTypeBaiduCloud) // 兼容旧值,等同于 [ACMEDns01ProviderTypeBaiduCloudDNS] ACMEDns01ProviderTypeBaiduCloudDNS = ACMEDns01ProviderType(AccessProviderTypeBaiduCloud + "-dns") + ACMEDns01ProviderTypeBookMyName = ACMEDns01ProviderType(AccessProviderTypeBookMyName) ACMEDns01ProviderTypeBunny = ACMEDns01ProviderType(AccessProviderTypeBunny) ACMEDns01ProviderTypeCloudflare = ACMEDns01ProviderType(AccessProviderTypeCloudflare) ACMEDns01ProviderTypeClouDNS = ACMEDns01ProviderType(AccessProviderTypeClouDNS) diff --git a/pkg/core/ssl-applicator/acme-dns01/providers/bookmyname/bookmyname.go b/pkg/core/ssl-applicator/acme-dns01/providers/bookmyname/bookmyname.go new file mode 100644 index 000000000..748ada8df --- /dev/null +++ b/pkg/core/ssl-applicator/acme-dns01/providers/bookmyname/bookmyname.go @@ -0,0 +1,40 @@ +package bookmyname + +import ( + "errors" + "time" + + "github.com/go-acme/lego/v4/providers/dns/bookmyname" + + "github.com/certimate-go/certimate/pkg/core" +) + +type ChallengeProviderConfig struct { + Username string `json:"username"` + Password string `json:"password"` + 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 := bookmyname.NewDefaultConfig() + providerConfig.Username = config.Username + providerConfig.Password = config.Password + if config.DnsPropagationTimeout != 0 { + providerConfig.PropagationTimeout = time.Duration(config.DnsPropagationTimeout) * time.Second + } + if config.DnsTTL != 0 { + providerConfig.TTL = int(config.DnsTTL) + } + + provider, err := bookmyname.NewDNSProviderConfig(providerConfig) + if err != nil { + return nil, err + } + + return provider, nil +} diff --git a/pkg/core/ssl-applicator/acme-dns01/providers/hetzner/hetzner.go b/pkg/core/ssl-applicator/acme-dns01/providers/hetzner/hetzner.go index 8c270e6a2..e8c0ada43 100644 --- a/pkg/core/ssl-applicator/acme-dns01/providers/hetzner/hetzner.go +++ b/pkg/core/ssl-applicator/acme-dns01/providers/hetzner/hetzner.go @@ -1,4 +1,4 @@ -package namedotcom +package hetzner import ( "errors" @@ -21,7 +21,7 @@ func NewChallengeProvider(config *ChallengeProviderConfig) (core.ACMEChallenger, } providerConfig := hetzner.NewDefaultConfig() - providerConfig.APIKey = config.ApiToken + providerConfig.APIToken = config.ApiToken if config.DnsPropagationTimeout != 0 { providerConfig.PropagationTimeout = time.Duration(config.DnsPropagationTimeout) * time.Second } diff --git a/ui/public/imgs/providers/bookmyname.png b/ui/public/imgs/providers/bookmyname.png new file mode 100644 index 0000000000000000000000000000000000000000..5f47787c69faea787554a26d0e93df64527d6ab6 GIT binary patch literal 522 zcmV+l0`>igP)g5G6$P|n z>l+2*eP{zrgNC2mbCKWA7e9jG>viPT_rv?OFE`SlO(2sCw{!DdFPFnjcoC&F+{W1^ zDw-B0P(X{~s2<#U^#RO}zUBrNgTqB}^ajXhjdHk+-OGeh(`4L)sQTnQB$4~65dVC~4ZW=3k9%)7!Fe+1k_Q5?K12*WYYVAmXOtlop7G}uUsH5_1` zJs@i0`{QYOAF|>=B`riD4#ybt>NBr_a2wCpGvY!@TBzY(P<+6A9Pa+2xEMo4vyWi* z+5bSp-CqINS@056?OZr>`L+4yA$syV7vL9$?{71N2WQNE*;0B`mnI`^Pj; z3a8Jn5^mbrKopK0BY5>I-le&Llh`S{14lSbQSllNQ4|FJb(*^F4@2-Zz{ejEng9R* M07*qoM6N<$g5QVTNB{r; literal 0 HcmV?d00001 diff --git a/ui/src/components/access/AccessForm.tsx b/ui/src/components/access/AccessForm.tsx index 2430221b0..e0eab008b 100644 --- a/ui/src/components/access/AccessForm.tsx +++ b/ui/src/components/access/AccessForm.tsx @@ -24,6 +24,7 @@ import AccessConfigFieldsProviderBaiduCloud from "./forms/AccessConfigFieldsProv import AccessConfigFieldsProviderBaishan from "./forms/AccessConfigFieldsProviderBaishan"; import AccessConfigFieldsProviderBaotaPanel from "./forms/AccessConfigFieldsProviderBaotaPanel"; import AccessConfigFieldsProviderBaotaWAF from "./forms/AccessConfigFieldsProviderBaotaWAF"; +import AccessConfigFieldsProviderBookMyName from "./forms/AccessConfigFieldsProviderBookMyName"; import AccessConfigFieldsProviderBunny from "./forms/AccessConfigFieldsProviderBunny"; import AccessConfigFieldsProviderBytePlus from "./forms/AccessConfigFieldsProviderBytePlus"; import AccessConfigFieldsProviderCacheFly from "./forms/AccessConfigFieldsProviderCacheFly"; @@ -174,6 +175,9 @@ const AccessForm = ({ className, style, disabled, initialValues, mode, usage, on case ACCESS_PROVIDERS.BAOTAWAF: { return ; } + case ACCESS_PROVIDERS.BOOKMYNAME: { + return ; + } case ACCESS_PROVIDERS.BUNNY: { return ; } diff --git a/ui/src/components/access/forms/AccessConfigFieldsProviderBookMyName.tsx b/ui/src/components/access/forms/AccessConfigFieldsProviderBookMyName.tsx new file mode 100644 index 000000000..34da50569 --- /dev/null +++ b/ui/src/components/access/forms/AccessConfigFieldsProviderBookMyName.tsx @@ -0,0 +1,62 @@ +import { getI18n, useTranslation } from "react-i18next"; +import { Form, Input } from "antd"; +import { createSchemaFieldRule } from "antd-zod"; +import { z } from "zod"; + +import { useFormNestedFieldsContext } from "./_context"; + +const AccessConfigFormFieldsProviderBookMyName = () => { + 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 { + username: "", + password: "", + }; +}; + +const getSchema = ({ i18n = getI18n() }: { i18n: ReturnType }) => { + const { t } = i18n; + + return z.object({ + username: z.string().nonempty(t("access.form.bookmyname_username.placeholder")), + password: z.string().nonempty(t("access.form.bookmyname_password.placeholder")), + }); +}; + +const _default = Object.assign(AccessConfigFormFieldsProviderBookMyName, { + getInitialValues, + getSchema, +}); + +export default _default; diff --git a/ui/src/domain/provider.ts b/ui/src/domain/provider.ts index 4ef6760eb..e9a777fb9 100644 --- a/ui/src/domain/provider.ts +++ b/ui/src/domain/provider.ts @@ -28,6 +28,7 @@ export const ACCESS_PROVIDERS = Object.freeze({ BAISHAN: "baishan", BAOTAPANEL: "baotapanel", BAOTAWAF: "baotawaf", + BOOKMYNAME: "bookmyname", BUNNY: "bunny", BYTEPLUS: "byteplus", CACHEFLY: "cachefly", @@ -164,6 +165,7 @@ export const accessProvidersMap: Maphttps://github.com/aaPanel/aaWAF/blob/main/API.md", + "access.form.bookmyname_username.label": "BookMyName username", + "access.form.bookmyname_username.placeholder": "Please enter BookMyName username", + "access.form.bookmyname_password.label": "BookMyName password", + "access.form.bookmyname_password.placeholder": "Please enter BookMyName password", "access.form.bunny_api_key.label": "Bunny API key", "access.form.bunny_api_key.placeholder": "Please enter Bunny API key", "access.form.bunny_api_key.tooltip": "For more information, see https://docs.bunny.net/reference/bunnynet-api-overview", diff --git a/ui/src/i18n/locales/en/nls.provider.json b/ui/src/i18n/locales/en/nls.provider.json index fe6c6caed..1f6c86b64 100644 --- a/ui/src/i18n/locales/en/nls.provider.json +++ b/ui/src/i18n/locales/en/nls.provider.json @@ -49,6 +49,7 @@ "provider.baotawaf": "aaWAF (aka BaotaWAF)", "provider.baotawaf.console": "aaWAF (aka BaotaWAF) - Console itself", "provider.baotawaf.site": "aaWAF (aka BaotaWAF) - Website", + "provider.bookmyname": "BookMyName", "provider.bunny": "Bunny", "provider.bunny.cdn": "Bunny - CDN (Content Delivery Network)", "provider.byteplus": "BytePlus", diff --git a/ui/src/i18n/locales/zh/nls.access.json b/ui/src/i18n/locales/zh/nls.access.json index 08e0906fc..a6666061e 100644 --- a/ui/src/i18n/locales/zh/nls.access.json +++ b/ui/src/i18n/locales/zh/nls.access.json @@ -133,8 +133,12 @@ "access.form.baotawaf_server_url.placeholder": "请输入堡塔云 WAF 服务地址", "access.form.baotawaf_server_url.help": "提示:请勿包含安全入口后缀。", "access.form.baotawaf_api_key.label": "堡塔云 WAF 接口密钥", - "access.form.baotawaf_api_key.placeholder": "请输入 堡塔云 WAF 接口密钥", + "access.form.baotawaf_api_key.placeholder": "请输入堡塔云 WAF 接口密钥", "access.form.baotawaf_api_key.tooltip": "这是什么?请参阅 https://github.com/aaPanel/aaWAF/blob/main/API.md", + "access.form.bookmyname_username.label": "BookMyName 用户名", + "access.form.bookmyname_username.placeholder": "请输入 BookMyName 用户名", + "access.form.bookmyname_password.label": "BookMyName 用户密码", + "access.form.bookmyname_password.placeholder": "请输入 BookMyName 用户密码", "access.form.bunny_api_key.label": "Bunny API Key", "access.form.bunny_api_key.placeholder": "请输入 Bunny API Key", "access.form.bunny_api_key.tooltip": "这是什么?请参阅 https://docs.bunny.net/reference/bunnynet-api-overview", diff --git a/ui/src/i18n/locales/zh/nls.provider.json b/ui/src/i18n/locales/zh/nls.provider.json index 3aecf1f8a..dc743940b 100644 --- a/ui/src/i18n/locales/zh/nls.provider.json +++ b/ui/src/i18n/locales/zh/nls.provider.json @@ -49,6 +49,7 @@ "provider.baotawaf": "堡塔云 WAF", "provider.baotawaf.console": "堡塔云 WAF - 面板自身", "provider.baotawaf.site": "堡塔云 WAF - 网站", + "provider.bookmyname": "BookMyName", "provider.bunny": "Bunny", "provider.bunny.cdn": "Bunny - 内容分发网络 CDN", "provider.byteplus": "BytePlus",