mirror of
https://github.com/certimate-go/certimate.git
synced 2026-09-24 23:10:13 +08:00
feat(provider): new acme dns-01 provider: dynadot
This commit is contained in:
@@ -0,0 +1,27 @@
|
||||
package certifiers
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/certimate-go/certimate/internal/domain"
|
||||
"github.com/certimate-go/certimate/pkg/core"
|
||||
"github.com/certimate-go/certimate/pkg/core/certifier/challengers/dns01/dynadot"
|
||||
xmaps "github.com/certimate-go/certimate/pkg/utils/maps"
|
||||
)
|
||||
|
||||
func init() {
|
||||
ACMEDns01Registries.MustRegister(domain.ACMEDns01ProviderTypeDynadot, func(options *ProviderFactoryOptions) (core.ACMEChallenger, error) {
|
||||
credentials := domain.AccessConfigForDynadot{}
|
||||
if err := xmaps.Populate(options.ProviderAccessConfig, &credentials); err != nil {
|
||||
return nil, fmt.Errorf("failed to populate provider access config: %w", err)
|
||||
}
|
||||
|
||||
provider, err := dynadot.NewChallenger(&dynadot.ChallengerConfig{
|
||||
ApiKey: credentials.ApiKey,
|
||||
ApiSecret: credentials.ApiSecret,
|
||||
DnsPropagationTimeout: options.DnsPropagationTimeout,
|
||||
DnsTTL: options.DnsTTL,
|
||||
})
|
||||
return provider, err
|
||||
})
|
||||
}
|
||||
@@ -225,6 +225,11 @@ type AccessConfigForDuckDNS struct {
|
||||
Token string `json:"token"`
|
||||
}
|
||||
|
||||
type AccessConfigForDynadot struct {
|
||||
ApiKey string `json:"apiKey"`
|
||||
ApiSecret string `json:"apiSecret"`
|
||||
}
|
||||
|
||||
type AccessConfigForDynu struct {
|
||||
ApiKey string `json:"apiKey"`
|
||||
}
|
||||
|
||||
@@ -50,6 +50,7 @@ const (
|
||||
AccessProviderTypeDogeCloud = AccessProviderType("dogecloud")
|
||||
AccessProviderTypeDokploy = AccessProviderType("dokploy")
|
||||
AccessProviderTypeDuckDNS = AccessProviderType("duckdns")
|
||||
AccessProviderTypeDynadot = AccessProviderType("dynadot")
|
||||
AccessProviderTypeDynu = AccessProviderType("dynu")
|
||||
AccessProviderTypeDynv6 = AccessProviderType("dynv6")
|
||||
AccessProviderTypeEmail = AccessProviderType("email")
|
||||
@@ -193,6 +194,7 @@ const (
|
||||
ACMEDns01ProviderTypeDNSLA = ACMEDns01ProviderType(AccessProviderTypeDNSLA)
|
||||
ACMEDns01ProviderTypeDNSMadeEasy = ACMEDns01ProviderType(AccessProviderTypeDNSMadeEasy)
|
||||
ACMEDns01ProviderTypeDuckDNS = ACMEDns01ProviderType(AccessProviderTypeDuckDNS)
|
||||
ACMEDns01ProviderTypeDynadot = ACMEDns01ProviderType(AccessProviderTypeDynadot)
|
||||
ACMEDns01ProviderTypeDynu = ACMEDns01ProviderType(AccessProviderTypeDynu)
|
||||
ACMEDns01ProviderTypeDynv6 = ACMEDns01ProviderType(AccessProviderTypeDynv6)
|
||||
ACMEDns01ProviderTypeGandinet = ACMEDns01ProviderType(AccessProviderTypeGandinet)
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
package dynadot
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/certimate-go/certimate/pkg/core/certifier"
|
||||
"github.com/certimate-go/certimate/pkg/core/certifier/challengers/dns01/dynadot/internal"
|
||||
)
|
||||
|
||||
type ChallengerConfig struct {
|
||||
ApiKey string `json:"apiKey"`
|
||||
ApiSecret string `json:"apiSecret"`
|
||||
DnsPropagationTimeout int `json:"dnsPropagationTimeout,omitempty"`
|
||||
DnsTTL int `json:"dnsTTL,omitempty"`
|
||||
}
|
||||
|
||||
func NewChallenger(config *ChallengerConfig) (certifier.ACMEChallenger, error) {
|
||||
if config == nil {
|
||||
return nil, fmt.Errorf("the configuration of the acme challenge provider is nil")
|
||||
}
|
||||
|
||||
providerConfig := internal.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 = config.DnsTTL
|
||||
}
|
||||
|
||||
provider, err := internal.NewDNSProviderConfig(providerConfig)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return provider, nil
|
||||
}
|
||||
@@ -0,0 +1,154 @@
|
||||
package internal
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/go-acme/lego/v4/challenge"
|
||||
"github.com/go-acme/lego/v4/challenge/dns01"
|
||||
"github.com/go-acme/lego/v4/platform/config/env"
|
||||
"github.com/samber/lo"
|
||||
|
||||
dynadotsdk "github.com/certimate-go/certimate/pkg/sdk3rd/dynadot"
|
||||
)
|
||||
|
||||
const (
|
||||
envNamespace = "DYNADOT_"
|
||||
|
||||
EnvAPIKey = envNamespace + "API_KEY"
|
||||
EnvAPISecret = envNamespace + "API_SECRET"
|
||||
|
||||
EnvTTL = envNamespace + "TTL"
|
||||
EnvPropagationTimeout = envNamespace + "PROPAGATION_TIMEOUT"
|
||||
EnvPollingInterval = envNamespace + "POLLING_INTERVAL"
|
||||
EnvHTTPTimeout = envNamespace + "HTTP_TIMEOUT"
|
||||
)
|
||||
|
||||
var _ challenge.ProviderTimeout = (*DNSProvider)(nil)
|
||||
|
||||
type Config struct {
|
||||
APIKey string
|
||||
APISecret string
|
||||
|
||||
PropagationTimeout time.Duration
|
||||
PollingInterval time.Duration
|
||||
TTL int
|
||||
HTTPTimeout time.Duration
|
||||
}
|
||||
|
||||
type DNSProvider struct {
|
||||
config *Config
|
||||
client *dynadotsdk.Client
|
||||
}
|
||||
|
||||
type dnsRecordCacheEntry struct {
|
||||
Zone string
|
||||
SubHost string
|
||||
Value string
|
||||
}
|
||||
|
||||
func NewDefaultConfig() *Config {
|
||||
return &Config{
|
||||
TTL: env.GetOrDefaultInt(EnvTTL, dns01.DefaultTTL),
|
||||
PropagationTimeout: env.GetOrDefaultSecond(EnvPropagationTimeout, dns01.DefaultPropagationTimeout),
|
||||
PollingInterval: env.GetOrDefaultSecond(EnvPollingInterval, dns01.DefaultPollingInterval),
|
||||
HTTPTimeout: env.GetOrDefaultSecond(EnvHTTPTimeout, 30*time.Second),
|
||||
}
|
||||
}
|
||||
|
||||
func NewDNSProvider() (*DNSProvider, error) {
|
||||
values, err := env.Get(EnvAPIKey, EnvAPISecret)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("dynadot: %w", err)
|
||||
}
|
||||
|
||||
config := NewDefaultConfig()
|
||||
config.APIKey = values[EnvAPIKey]
|
||||
config.APISecret = values[EnvAPISecret]
|
||||
|
||||
return NewDNSProviderConfig(config)
|
||||
}
|
||||
|
||||
func NewDNSProviderConfig(config *Config) (*DNSProvider, error) {
|
||||
if config == nil {
|
||||
return nil, fmt.Errorf("dynadot: the configuration of the DNS provider is nil")
|
||||
}
|
||||
|
||||
client, err := dynadotsdk.NewClient(config.APIKey, config.APISecret)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("dynadot: %w", err)
|
||||
} else {
|
||||
client.SetTimeout(config.HTTPTimeout)
|
||||
}
|
||||
|
||||
return &DNSProvider{
|
||||
config: config,
|
||||
client: client,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (d *DNSProvider) Present(domain, token, keyAuth string) error {
|
||||
info := dns01.GetChallengeInfo(domain, keyAuth)
|
||||
|
||||
authZone, err := dns01.FindZoneByFqdn(info.EffectiveFQDN)
|
||||
if err != nil {
|
||||
return fmt.Errorf("dynadot: could not find zone for domain %q: %w", domain, err)
|
||||
}
|
||||
|
||||
subDomain, err := dns01.ExtractSubDomain(info.EffectiveFQDN, authZone)
|
||||
if err != nil {
|
||||
return fmt.Errorf("dynadot: %w", err)
|
||||
}
|
||||
|
||||
// REF: https://www.dynadot.com/domain/api-document (set_dns)
|
||||
request := &dynadotsdk.SetDnsRequest{
|
||||
SubList: []*dynadotsdk.DnsSubRecord{
|
||||
{
|
||||
SubHost: subDomain,
|
||||
RecordType: "TXT",
|
||||
RecordValue1: info.Value,
|
||||
},
|
||||
},
|
||||
TTL: lo.ToPtr(int64(d.config.TTL)),
|
||||
AddDnsToCurrentSetting: lo.ToPtr(true),
|
||||
}
|
||||
if _, err := d.client.SetDns(dns01.UnFqdn(authZone), request); err != nil {
|
||||
return fmt.Errorf("dynadot: error when create record: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error {
|
||||
info := dns01.GetChallengeInfo(domain, keyAuth)
|
||||
|
||||
authZone, err := dns01.FindZoneByFqdn(info.EffectiveFQDN)
|
||||
if err != nil {
|
||||
return fmt.Errorf("dynadot: could not find zone for domain %q: %w", domain, err)
|
||||
}
|
||||
|
||||
subDomain, err := dns01.ExtractSubDomain(info.EffectiveFQDN, authZone)
|
||||
if err != nil {
|
||||
return fmt.Errorf("dynadot: %w", err)
|
||||
}
|
||||
|
||||
// REF: https://www.dynadot.com/domain/api-document (remove_dns)
|
||||
request := &dynadotsdk.RemoveDnsRequest{
|
||||
SubList: []*dynadotsdk.DnsSubRecord{
|
||||
{
|
||||
SubHost: subDomain,
|
||||
RecordType: "TXT",
|
||||
RecordValue1: info.Value,
|
||||
},
|
||||
},
|
||||
}
|
||||
if _, err := d.client.RemoveDns(dns01.UnFqdn(authZone), request); err != nil {
|
||||
return fmt.Errorf("dynadot: error when delete record: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *DNSProvider) Timeout() (timeout, interval time.Duration) {
|
||||
return d.config.PropagationTimeout, d.config.PollingInterval
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
package dynadot
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
type RemoveDnsRequest struct {
|
||||
DnsMainList []*DnsMainRecord `json:"dns_main_list,omitempty"`
|
||||
SubList []*DnsSubRecord `json:"sub_list,omitempty"`
|
||||
}
|
||||
|
||||
type RemoveDnsResponse struct {
|
||||
sdkResponseBase
|
||||
}
|
||||
|
||||
func (c *Client) RemoveDns(domain string, req *RemoveDnsRequest) (*RemoveDnsResponse, error) {
|
||||
return c.RemoveDnsWithContext(context.Background(), domain, req)
|
||||
}
|
||||
|
||||
func (c *Client) RemoveDnsWithContext(ctx context.Context, domain string, req *RemoveDnsRequest) (*RemoveDnsResponse, error) {
|
||||
if domain == "" {
|
||||
return nil, fmt.Errorf("sdkerr: unset domain")
|
||||
}
|
||||
|
||||
httpreq, err := c.newRequest(http.MethodDelete, fmt.Sprintf("/restful/v2/domains/%s/records", domain), req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
httpreq.SetContext(ctx)
|
||||
}
|
||||
|
||||
result := &RemoveDnsResponse{}
|
||||
if _, err := c.doRequestWithResult(httpreq, result); err != nil {
|
||||
return result, err
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
package dynadot
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
type SetDnsRequest struct {
|
||||
DnsMainList []*DnsMainRecord `json:"dns_main_list,omitempty"`
|
||||
SubList []*DnsSubRecord `json:"sub_list,omitempty"`
|
||||
TTL *int64 `json:"ttl,omitempty"`
|
||||
AddDnsToCurrentSetting *bool `json:"add_dns_to_current_setting,omitempty"`
|
||||
}
|
||||
|
||||
type SetDnsResponse struct {
|
||||
sdkResponseBase
|
||||
}
|
||||
|
||||
func (c *Client) SetDns(domain string, req *SetDnsRequest) (*SetDnsResponse, error) {
|
||||
return c.SetDnsWithContext(context.Background(), domain, req)
|
||||
}
|
||||
|
||||
func (c *Client) SetDnsWithContext(ctx context.Context, domain string, req *SetDnsRequest) (*SetDnsResponse, error) {
|
||||
if domain == "" {
|
||||
return nil, fmt.Errorf("sdkerr: unset domain")
|
||||
}
|
||||
|
||||
httpreq, err := c.newRequest(http.MethodPost, fmt.Sprintf("/restful/v2/domains/%s/records", domain), req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
httpreq.SetContext(ctx)
|
||||
}
|
||||
|
||||
result := &SetDnsResponse{}
|
||||
if _, err := c.doRequestWithResult(httpreq, result); err != nil {
|
||||
return result, err
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
@@ -0,0 +1,149 @@
|
||||
package dynadot
|
||||
|
||||
import (
|
||||
"crypto/hmac"
|
||||
"crypto/sha256"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/go-resty/resty/v2"
|
||||
|
||||
"github.com/certimate-go/certimate/internal/app"
|
||||
)
|
||||
|
||||
type Client struct {
|
||||
apiKey string
|
||||
apiSecret string
|
||||
|
||||
client *resty.Client
|
||||
}
|
||||
|
||||
func NewClient(apiKey, apiSecret string) (*Client, error) {
|
||||
if apiKey == "" {
|
||||
return nil, fmt.Errorf("sdkerr: unset apiKey")
|
||||
}
|
||||
if apiSecret == "" {
|
||||
return nil, fmt.Errorf("sdkerr: unset apiSecret")
|
||||
}
|
||||
|
||||
client := resty.New().
|
||||
SetBaseURL("https://api.dynadot.com").
|
||||
SetHeader("Accept", "application/json").
|
||||
SetHeader("Content-Type", "application/json").
|
||||
SetHeader("User-Agent", app.AppUserAgent)
|
||||
|
||||
return &Client{
|
||||
apiKey: apiKey,
|
||||
apiSecret: apiSecret,
|
||||
client: client,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (c *Client) SetTimeout(timeout time.Duration) *Client {
|
||||
c.client.SetTimeout(timeout)
|
||||
return c
|
||||
}
|
||||
|
||||
func (c *Client) newRequest(method string, path string, params any) (*resty.Request, error) {
|
||||
if method == "" {
|
||||
return nil, fmt.Errorf("sdkerr: unset method")
|
||||
}
|
||||
if path == "" {
|
||||
return nil, fmt.Errorf("sdkerr: unset path")
|
||||
}
|
||||
|
||||
var body string
|
||||
if params != nil {
|
||||
jsonb, err := json.Marshal(params)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("sdkerr: failed to marshal request body: %w", err)
|
||||
}
|
||||
body = string(jsonb)
|
||||
}
|
||||
|
||||
req := c.client.R()
|
||||
req.Method = method
|
||||
req.URL = path
|
||||
req.SetHeader("Authorization", "Bearer "+c.apiKey)
|
||||
req.SetHeader("X-Signature", generateSignature(c.apiKey, c.apiSecret, path, "", body))
|
||||
if body != "" {
|
||||
req.SetBody(body)
|
||||
}
|
||||
return req, nil
|
||||
}
|
||||
|
||||
func (c *Client) doRequest(req *resty.Request) (*resty.Response, error) {
|
||||
if req == nil {
|
||||
return nil, fmt.Errorf("sdkerr: nil request")
|
||||
}
|
||||
|
||||
// WARN:
|
||||
// PLEASE DO NOT USE `req.SetBody` or `req.SetFormData` HERE! USE `newRequest` INSTEAD.
|
||||
// PLEASE DO NOT USE `req.SetResult` or `req.SetError` HERE! USE `doRequestWithResult` INSTEAD.
|
||||
|
||||
resp, err := req.Send()
|
||||
if err != nil {
|
||||
return resp, fmt.Errorf("sdkerr: failed to send request: %w", err)
|
||||
} else if resp.IsError() {
|
||||
return resp, fmt.Errorf("sdkerr: unexpected status code: %d (resp: %s)", resp.StatusCode(), resp.String())
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
func (c *Client) doRequestWithResult(req *resty.Request, res sdkResponse) (*resty.Response, error) {
|
||||
if req == nil {
|
||||
return nil, fmt.Errorf("sdkerr: nil request")
|
||||
}
|
||||
|
||||
resp, err := c.doRequest(req)
|
||||
if err != nil {
|
||||
if resp != nil {
|
||||
json.Unmarshal(resp.Body(), &res)
|
||||
}
|
||||
return resp, err
|
||||
}
|
||||
|
||||
if len(resp.Body()) != 0 {
|
||||
if err := json.Unmarshal(resp.Body(), &res); err != nil {
|
||||
return resp, fmt.Errorf("sdkerr: failed to unmarshal response: %w (resp: %s)", err, resp.String())
|
||||
} else {
|
||||
// NOTE: Unlike most providers in this project (which use `0` for success),
|
||||
// Dynadot mirrors HTTP-style status codes inside the response body and
|
||||
// always returns `200` on success.
|
||||
// REF: https://www.dynadot.com/domain/api-document
|
||||
if tcode := res.GetCode(); tcode != 200 {
|
||||
return resp, fmt.Errorf("sdkerr: api error: code='%d', message='%s'", tcode, res.GetMessage())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// generateSignature builds the X-Signature header value as required by the
|
||||
// Dynadot RESTful v2 API. It signs the concatenation of:
|
||||
//
|
||||
// apiKey + "\n" + fullPathAndQuery + "\n" + xRequestId + "\n" + requestBody
|
||||
//
|
||||
// using HMAC-SHA256 with the API secret as key, then encodes the result with
|
||||
// standard Base64.
|
||||
//
|
||||
// REF: https://www.dynadot.com/domain/api-document
|
||||
func generateSignature(apiKey, apiSecret, fullPathAndQuery, xRequestId, requestBody string) string {
|
||||
var sb strings.Builder
|
||||
sb.WriteString(apiKey)
|
||||
sb.WriteString("\n")
|
||||
sb.WriteString(fullPathAndQuery)
|
||||
sb.WriteString("\n")
|
||||
sb.WriteString(xRequestId)
|
||||
sb.WriteString("\n")
|
||||
sb.WriteString(requestBody)
|
||||
|
||||
mac := hmac.New(sha256.New, []byte(apiSecret))
|
||||
mac.Write([]byte(sb.String()))
|
||||
return base64.StdEncoding.EncodeToString(mac.Sum(nil))
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
package dynadot
|
||||
|
||||
type sdkResponse interface {
|
||||
GetCode() int
|
||||
GetMessage() string
|
||||
}
|
||||
|
||||
type sdkResponseBase struct {
|
||||
Code int `json:"code"`
|
||||
Message string `json:"message"`
|
||||
}
|
||||
|
||||
func (r *sdkResponseBase) GetCode() int {
|
||||
return r.Code
|
||||
}
|
||||
|
||||
func (r *sdkResponseBase) GetMessage() string {
|
||||
return r.Message
|
||||
}
|
||||
|
||||
var _ sdkResponse = (*sdkResponseBase)(nil)
|
||||
|
||||
type DnsMainRecord struct {
|
||||
RecordType string `json:"record_type"`
|
||||
RecordValue1 string `json:"record_value1"`
|
||||
RecordValue2 string `json:"record_value2,omitempty"`
|
||||
}
|
||||
|
||||
type DnsSubRecord struct {
|
||||
SubHost string `json:"sub_host"`
|
||||
RecordType string `json:"record_type"`
|
||||
RecordValue1 string `json:"record_value1"`
|
||||
RecordValue2 string `json:"record_value2,omitempty"`
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" viewBox="0 0 256 256" width="200" height="200"><path fill="rgb(4,18,65)" stroke="rgb(4,18,65)" stroke-width="1" opacity="1" d="M 0 0 L 256 0 L 256 256 L 0 256 L 0 0 Z M 152 49 L 145 51 L 129 59 L 74 89 L 88 115 L 88 117 Q 75 121 66 129 L 55 142 L 51 151 L 49 161 Q 49 179 57 190 Q 67 204 88 208 L 101 208 L 111 205 L 180 169 L 166 140 L 188 129 Q 201 120 205 102 L 205 85 Q 203 74 197 67 Q 189 56 177 51 Q 167 47 152 49 Z " /><path fill="rgb(4,18,65)" stroke="rgb(4,18,65)" stroke-width="1" opacity="1" d="M 155.5 65 Q 176.7 63.8 184 76.5 Q 189.5 83 189 95.5 L 186 105.5 L 181 113 L 158.5 126 L 151 111.5 L 146.5 106 L 133.5 99 L 122.5 99 L 103.5 108 L 97 96.5 L 97 94 L 155.5 65 Z " /><path fill="rgb(4,18,65)" stroke="rgb(4,18,65)" stroke-width="1" opacity="1" d="M 126.5 115 Q 133.4 115.6 137 119.5 Q 141.3 122.7 140 131.5 L 134.5 139 Q 131.5 142 124.5 141 L 118 138 L 115 132.5 L 115 123.5 L 120.5 117 L 126.5 115 Z " /><path fill="rgb(4,18,65)" stroke="rgb(4,18,65)" stroke-width="1" opacity="1" d="M 95.5 131 Q 97.8 130.3 97 132.5 Q 101.6 145.4 111.5 153 Q 118.2 159.3 133.5 157 L 150.5 148 L 158 162 L 103.5 190 Q 97.2 192.7 86.5 191 Q 76.6 187.9 71 180.5 Q 64.1 173.4 66 157.5 Q 68.6 146.6 76.5 141 L 95.5 131 Z " /><path fill="rgb(249,251,252)" stroke="rgb(249,251,252)" stroke-width="1" opacity="1" d="M 151.5 49 Q 167 47 176.5 51 Q 189.4 56.1 197 66.5 Q 203 73.5 205 84.5 L 205 101.5 Q 200.6 119.6 187.5 129 L 166 140 L 180 168.5 L 110.5 205 L 100.5 208 L 87.5 208 Q 66.9 204.1 57 189.5 Q 48.8 179.2 49 160.5 L 51 150.5 L 55 141.5 L 65.5 129 Q 75.2 121 88 117 L 88 114.5 L 74 88.5 L 128.5 59 L 144.5 51 L 151.5 49 Z M 156 65 L 97 94 L 97 97 L 104 108 L 123 99 L 134 99 L 147 106 L 151 112 L 159 126 L 181 113 L 186 106 L 189 96 Q 190 83 184 77 Q 177 64 156 65 Z M 127 115 L 121 117 L 115 124 L 115 133 L 118 138 L 125 141 Q 132 142 135 139 L 140 132 Q 141 123 137 120 Q 133 116 127 115 Z M 96 131 L 77 141 Q 69 147 66 158 Q 64 173 71 181 Q 77 188 87 191 Q 97 193 104 190 L 158 162 L 151 148 L 134 157 Q 118 159 112 153 Q 102 145 97 133 Q 98 130 96 131 Z " /></svg>
|
||||
|
After Width: | Height: | Size: 2.1 KiB |
@@ -42,6 +42,7 @@ import AccessConfigFieldsProviderDNSMadeEasy from "./AccessConfigFieldsProviderD
|
||||
import AccessConfigFieldsProviderDogeCloud from "./AccessConfigFieldsProviderDogeCloud";
|
||||
import AccessConfigFieldsProviderDokploy from "./AccessConfigFieldsProviderDokploy";
|
||||
import AccessConfigFieldsProviderDuckDNS from "./AccessConfigFieldsProviderDuckDNS";
|
||||
import AccessConfigFieldsProviderDynadot from "./AccessConfigFieldsProviderDynadot";
|
||||
import AccessConfigFieldsProviderDynu from "./AccessConfigFieldsProviderDynu";
|
||||
import AccessConfigFieldsProviderDynv6 from "./AccessConfigFieldsProviderDynv6";
|
||||
import AccessConfigFieldsProviderEmail from "./AccessConfigFieldsProviderEmail";
|
||||
@@ -159,6 +160,7 @@ const providerComponentMap: Partial<Record<AccessProviderType, React.ComponentTy
|
||||
[ACCESS_PROVIDERS.DOKPLOY]: AccessConfigFieldsProviderDokploy,
|
||||
[ACCESS_PROVIDERS.DOGECLOUD]: AccessConfigFieldsProviderDogeCloud,
|
||||
[ACCESS_PROVIDERS.DUCKDNS]: AccessConfigFieldsProviderDuckDNS,
|
||||
[ACCESS_PROVIDERS.DYNADOT]: AccessConfigFieldsProviderDynadot,
|
||||
[ACCESS_PROVIDERS.DYNU]: AccessConfigFieldsProviderDynu,
|
||||
[ACCESS_PROVIDERS.DYNV6]: AccessConfigFieldsProviderDynv6,
|
||||
[ACCESS_PROVIDERS.EMAIL]: AccessConfigFieldsProviderEmail,
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
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 AccessConfigFormFieldsProviderDynadot = () => {
|
||||
const { i18n, t } = useTranslation();
|
||||
|
||||
const { parentNamePath } = useFormNestedFieldsContext();
|
||||
const formSchema = z.object({
|
||||
[parentNamePath]: getSchema({ i18n }),
|
||||
});
|
||||
const formRule = createSchemaFieldRule(formSchema);
|
||||
const initialValues = getInitialValues();
|
||||
|
||||
return (
|
||||
<>
|
||||
<Form.Item
|
||||
name={[parentNamePath, "apiKey"]}
|
||||
initialValue={initialValues.apiKey}
|
||||
label={t("access.form.dynadot_api_key.label")}
|
||||
rules={[formRule]}
|
||||
tooltip={<span dangerouslySetInnerHTML={{ __html: t("access.form.dynadot_api_key.tooltip") }}></span>}
|
||||
>
|
||||
<Input autoComplete="new-password" placeholder={t("access.form.dynadot_api_key.placeholder")} />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
name={[parentNamePath, "apiSecret"]}
|
||||
initialValue={initialValues.apiSecret}
|
||||
label={t("access.form.dynadot_api_secret.label")}
|
||||
rules={[formRule]}
|
||||
tooltip={<span dangerouslySetInnerHTML={{ __html: t("access.form.dynadot_api_secret.tooltip") }}></span>}
|
||||
>
|
||||
<Input.Password autoComplete="new-password" placeholder={t("access.form.dynadot_api_secret.placeholder")} />
|
||||
</Form.Item>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const getInitialValues = (): Nullish<z.infer<ReturnType<typeof getSchema>>> => {
|
||||
return {
|
||||
apiKey: "",
|
||||
apiSecret: "",
|
||||
};
|
||||
};
|
||||
|
||||
const getSchema = ({ i18n = getI18n() }: { i18n: ReturnType<typeof getI18n> }) => {
|
||||
const { t: _ } = i18n;
|
||||
|
||||
return z.object({
|
||||
apiKey: z.string().nonempty(),
|
||||
apiSecret: z.string().nonempty(),
|
||||
});
|
||||
};
|
||||
|
||||
const _default = Object.assign(AccessConfigFormFieldsProviderDynadot, {
|
||||
getInitialValues,
|
||||
getSchema,
|
||||
});
|
||||
|
||||
export default _default;
|
||||
@@ -55,6 +55,7 @@ export const ACCESS_PROVIDERS = Object.freeze({
|
||||
DOGECLOUD: "dogecloud",
|
||||
DOKPLOY: "dokploy",
|
||||
DUCKDNS: "duckdns",
|
||||
DYNADOT: "dynadot",
|
||||
DYNU: "dynu",
|
||||
DYNV6: "dynv6",
|
||||
EMAIL: "email",
|
||||
@@ -218,6 +219,7 @@ export const accessProvidersMap: Map<AccessProvider["type"] | string, AccessProv
|
||||
[ACCESS_PROVIDERS.DNSEXIT, "provider.dnsexit", "/imgs/providers/dnsexit.png", [ACCESS_USAGES.DNS]],
|
||||
[ACCESS_PROVIDERS.DNSMADEEASY, "provider.dnsmadeeasy", "/imgs/providers/dnsmadeeasy.png", [ACCESS_USAGES.DNS]],
|
||||
[ACCESS_PROVIDERS.DUCKDNS, "provider.duckdns", "/imgs/providers/duckdns.png", [ACCESS_USAGES.DNS]],
|
||||
[ACCESS_PROVIDERS.DYNADOT, "provider.dynadot", "/imgs/providers/dynadot.svg", [ACCESS_USAGES.DNS]],
|
||||
[ACCESS_PROVIDERS.DYNU, "provider.dynu", "/imgs/providers/dynu.png", [ACCESS_USAGES.DNS]],
|
||||
[ACCESS_PROVIDERS.DYNV6, "provider.dynv6", "/imgs/providers/dynv6.svg", [ACCESS_USAGES.DNS]],
|
||||
[ACCESS_PROVIDERS.GANDINET, "provider.gandinet", "/imgs/providers/gandinet.svg", [ACCESS_USAGES.DNS]],
|
||||
@@ -383,6 +385,7 @@ export const ACME_DNS01_PROVIDERS = Object.freeze({
|
||||
DNSLA: `${ACCESS_PROVIDERS.DNSLA}`,
|
||||
DNSMADEEASY: `${ACCESS_PROVIDERS.DNSMADEEASY}`,
|
||||
DUCKDNS: `${ACCESS_PROVIDERS.DUCKDNS}`,
|
||||
DYNADOT: `${ACCESS_PROVIDERS.DYNADOT}`,
|
||||
DYNU: `${ACCESS_PROVIDERS.DYNU}`,
|
||||
DYNV6: `${ACCESS_PROVIDERS.DYNV6}`,
|
||||
GANDINET: `${ACCESS_PROVIDERS.GANDINET}`,
|
||||
@@ -462,6 +465,7 @@ export const acmeDns01ProvidersMap: Map<ACMEDns01Provider["type"] | string, ACME
|
||||
[ACME_DNS01_PROVIDERS.DNSEXIT, "provider.dnsexit"],
|
||||
[ACME_DNS01_PROVIDERS.DNSMADEEASY, "provider.dnsmadeeasy"],
|
||||
[ACME_DNS01_PROVIDERS.DUCKDNS, "provider.duckdns"],
|
||||
[ACME_DNS01_PROVIDERS.DYNADOT, "provider.dynadot"],
|
||||
[ACME_DNS01_PROVIDERS.DYNU, "provider.dynu"],
|
||||
[ACME_DNS01_PROVIDERS.DYNV6, "provider.dynv6"],
|
||||
[ACME_DNS01_PROVIDERS.GANDINET, "provider.gandinet"],
|
||||
|
||||
@@ -498,6 +498,16 @@
|
||||
"placeholder": "Please enter DuckDNS token",
|
||||
"tooltip": "For more information, see <a href=\"https://www.duckdns.org/spec.jsp\" target=\"_blank\">https://www.duckdns.org/spec.jsp</a>"
|
||||
},
|
||||
"dynadot_api_key": {
|
||||
"label": "Dynadot API key",
|
||||
"placeholder": "Please enter Dynadot API key",
|
||||
"tooltip": "For more information, see <a href=\"https://www.dynadot.com/domain/api-document\" target=\"_blank\">https://www.dynadot.com/domain/api-document</a>"
|
||||
},
|
||||
"dynadot_api_secret": {
|
||||
"label": "Dynadot API secret",
|
||||
"placeholder": "Please enter Dynadot API secret",
|
||||
"tooltip": "For more information, see <a href=\"https://www.dynadot.com/domain/api-document\" target=\"_blank\">https://www.dynadot.com/domain/api-document</a>"
|
||||
},
|
||||
"dynu_api_key": {
|
||||
"label": "Dynu API key",
|
||||
"placeholder": "Please enter Dynu API key",
|
||||
|
||||
@@ -119,6 +119,7 @@
|
||||
"dogecloud": "Doge Cloud",
|
||||
"dokploy": "Dokploy",
|
||||
"duckdns": "Duck DNS",
|
||||
"dynadot": "Dynadot",
|
||||
"dynu": "Dynu",
|
||||
"dynv6": "dynv6",
|
||||
"email": "Email (SMTP)",
|
||||
|
||||
@@ -496,6 +496,16 @@
|
||||
"placeholder": "请输入 DuckDNS Token",
|
||||
"tooltip": "这是什么?请参阅 <a href=\"https://www.duckdns.org/spec.jsp\" target=\"_blank\">https://www.duckdns.org/spec.jsp</a>"
|
||||
},
|
||||
"dynadot_api_key": {
|
||||
"label": "Dynadot API Key",
|
||||
"placeholder": "请输入 Dynadot API Key",
|
||||
"tooltip": "这是什么?请参阅 <a href=\"https://www.dynadot.com/zh/domain/api-document\" target=\"_blank\">https://www.dynadot.com/zh/domain/api-document</a>"
|
||||
},
|
||||
"dynadot_api_secret": {
|
||||
"label": "Dynadot API Secret",
|
||||
"placeholder": "请输入 Dynadot API Secret",
|
||||
"tooltip": "这是什么?请参阅 <a href=\"https://www.dynadot.com/zh/domain/api-document\" target=\"_blank\">https://www.dynadot.com/zh/domain/api-document</a>"
|
||||
},
|
||||
"dynu_api_key": {
|
||||
"label": "Dynu API Key",
|
||||
"placeholder": "请输入 Dynu API Key",
|
||||
|
||||
@@ -119,6 +119,7 @@
|
||||
"dogecloud_cdn": "多吉云 - 内容分发网络 CDN",
|
||||
"dokploy": "Dokploy",
|
||||
"duckdns": "Duck DNS",
|
||||
"dynadot": "Dynadot",
|
||||
"dynu": "Dynu",
|
||||
"dynv6": "dynv6",
|
||||
"email": "邮件(SMTP)",
|
||||
|
||||
Reference in New Issue
Block a user