diff --git a/internal/applicant/providers.go b/internal/applicant/providers.go
index 0af5422c..af4aa234 100644
--- a/internal/applicant/providers.go
+++ b/internal/applicant/providers.go
@@ -30,6 +30,7 @@ import (
pPowerDNS "github.com/usual2970/certimate/internal/pkg/core/applicant/acme-dns-01/lego-providers/powerdns"
pRainYun "github.com/usual2970/certimate/internal/pkg/core/applicant/acme-dns-01/lego-providers/rainyun"
pTencentCloud "github.com/usual2970/certimate/internal/pkg/core/applicant/acme-dns-01/lego-providers/tencentcloud"
+ pVercel "github.com/usual2970/certimate/internal/pkg/core/applicant/acme-dns-01/lego-providers/vercel"
pVolcEngine "github.com/usual2970/certimate/internal/pkg/core/applicant/acme-dns-01/lego-providers/volcengine"
pWestcn "github.com/usual2970/certimate/internal/pkg/core/applicant/acme-dns-01/lego-providers/westcn"
"github.com/usual2970/certimate/internal/pkg/utils/maputil"
@@ -425,6 +426,22 @@ func createApplicant(options *applicantOptions) (challenge.Provider, error) {
return applicant, err
}
+ case domain.ApplyDNSProviderTypeVercel:
+ {
+ access := domain.AccessConfigForVercel{}
+ if err := maputil.Populate(options.ProviderAccessConfig, &access); err != nil {
+ return nil, fmt.Errorf("failed to populate provider access config: %w", err)
+ }
+
+ applicant, err := pVercel.NewChallengeProvider(&pVercel.ChallengeProviderConfig{
+ ApiAccessToken: access.ApiAccessToken,
+ TeamId: access.TeamId,
+ DnsPropagationTimeout: options.DnsPropagationTimeout,
+ DnsTTL: options.DnsTTL,
+ })
+ return applicant, err
+ }
+
case domain.ApplyDNSProviderTypeVolcEngine, domain.ApplyDNSProviderTypeVolcEngineDNS:
{
access := domain.AccessConfigForVolcEngine{}
diff --git a/internal/domain/access.go b/internal/domain/access.go
index ac4cc71c..4b0b08b9 100644
--- a/internal/domain/access.go
+++ b/internal/domain/access.go
@@ -219,6 +219,11 @@ type AccessConfigForUpyun struct {
Password string `json:"password"`
}
+type AccessConfigForVercel struct {
+ ApiAccessToken string `json:"apiAccessToken"`
+ TeamId string `json:"teamId,omitempty"`
+}
+
type AccessConfigForVolcEngine struct {
AccessKeyId string `json:"accessKeyId"`
SecretAccessKey string `json:"secretAccessKey"`
diff --git a/internal/domain/provider.go b/internal/domain/provider.go
index 64013967..af877778 100644
--- a/internal/domain/provider.go
+++ b/internal/domain/provider.go
@@ -54,7 +54,7 @@ const (
AccessProviderTypeTencentCloud = AccessProviderType("tencentcloud")
AccessProviderTypeUCloud = AccessProviderType("ucloud")
AccessProviderTypeUpyun = AccessProviderType("upyun")
- AccessProviderTypeVercel = AccessProviderType("vercel") // Vercel(预留)
+ AccessProviderTypeVercel = AccessProviderType("vercel")
AccessProviderTypeVolcEngine = AccessProviderType("volcengine")
AccessProviderTypeWebhook = AccessProviderType("webhook")
AccessProviderTypeWestcn = AccessProviderType("westcn")
diff --git a/internal/pkg/core/applicant/acme-dns-01/lego-providers/vercel/vercel.go b/internal/pkg/core/applicant/acme-dns-01/lego-providers/vercel/vercel.go
new file mode 100644
index 00000000..35fb5f02
--- /dev/null
+++ b/internal/pkg/core/applicant/acme-dns-01/lego-providers/vercel/vercel.go
@@ -0,0 +1,38 @@
+package vercel
+
+import (
+ "time"
+
+ "github.com/go-acme/lego/v4/challenge"
+ "github.com/go-acme/lego/v4/providers/dns/vercel"
+)
+
+type ChallengeProviderConfig struct {
+ ApiAccessToken string `json:"apiAccessToken"`
+ TeamId string `json:"teamId,omitempty"`
+ DnsPropagationTimeout int32 `json:"dnsPropagationTimeout,omitempty"`
+ DnsTTL int32 `json:"dnsTTL,omitempty"`
+}
+
+func NewChallengeProvider(config *ChallengeProviderConfig) (challenge.Provider, error) {
+ if config == nil {
+ panic("config is nil")
+ }
+
+ providerConfig := vercel.NewDefaultConfig()
+ providerConfig.AuthToken = config.ApiAccessToken
+ providerConfig.TeamID = config.TeamId
+ if config.DnsPropagationTimeout != 0 {
+ providerConfig.PropagationTimeout = time.Duration(config.DnsPropagationTimeout) * time.Second
+ }
+ if config.DnsTTL != 0 {
+ providerConfig.TTL = int(config.DnsTTL)
+ }
+
+ provider, err := vercel.NewDNSProviderConfig(providerConfig)
+ if err != nil {
+ return nil, err
+ }
+
+ return provider, nil
+}
diff --git a/ui/public/imgs/providers/vercel.svg b/ui/public/imgs/providers/vercel.svg
new file mode 100644
index 00000000..65731f98
--- /dev/null
+++ b/ui/public/imgs/providers/vercel.svg
@@ -0,0 +1 @@
+
diff --git a/ui/src/components/access/AccessForm.tsx b/ui/src/components/access/AccessForm.tsx
index dbf63692..487d449a 100644
--- a/ui/src/components/access/AccessForm.tsx
+++ b/ui/src/components/access/AccessForm.tsx
@@ -48,6 +48,7 @@ import AccessFormSSHConfig from "./AccessFormSSHConfig";
import AccessFormTencentCloudConfig from "./AccessFormTencentCloudConfig";
import AccessFormUCloudConfig from "./AccessFormUCloudConfig";
import AccessFormUpyunConfig from "./AccessFormUpyunConfig";
+import AccessFormVercelConfig from "./AccessFormVercelConfig";
import AccessFormVolcEngineConfig from "./AccessFormVolcEngineConfig";
import AccessFormWebhookConfig from "./AccessFormWebhookConfig";
import AccessFormWestcnConfig from "./AccessFormWestcnConfig";
@@ -182,6 +183,8 @@ const AccessForm = forwardRef(({ className,
return ;
case ACCESS_PROVIDERS.UPYUN:
return ;
+ case ACCESS_PROVIDERS.VERCEL:
+ return ;
case ACCESS_PROVIDERS.VOLCENGINE:
return ;
case ACCESS_PROVIDERS.WEBHOOK:
diff --git a/ui/src/components/access/AccessFormUCloudConfig.tsx b/ui/src/components/access/AccessFormUCloudConfig.tsx
index f230e7c2..495d21b9 100644
--- a/ui/src/components/access/AccessFormUCloudConfig.tsx
+++ b/ui/src/components/access/AccessFormUCloudConfig.tsx
@@ -28,9 +28,9 @@ const AccessFormUCloudConfig = ({ form: formInst, formName, disabled, initialVal
const formSchema = z.object({
privateKey: z
.string()
- .trim()
.min(1, t("access.form.ucloud_private_key.placeholder"))
- .max(64, t("common.errmsg.string_max", { max: 64 })),
+ .max(64, t("common.errmsg.string_max", { max: 64 }))
+ .trim(),
publicKey: z
.string()
.min(1, t("access.form.ucloud_public_key.placeholder"))
diff --git a/ui/src/components/access/AccessFormVercelConfig.tsx b/ui/src/components/access/AccessFormVercelConfig.tsx
new file mode 100644
index 00000000..b1ed7b6f
--- /dev/null
+++ b/ui/src/components/access/AccessFormVercelConfig.tsx
@@ -0,0 +1,75 @@
+import { useTranslation } from "react-i18next";
+import { Form, type FormInstance, Input } from "antd";
+import { createSchemaFieldRule } from "antd-zod";
+import { z } from "zod";
+
+import { type AccessConfigForVercel } from "@/domain/access";
+
+type AccessFormVercelConfigFieldValues = Nullish;
+
+export type AccessFormVercelConfigProps = {
+ form: FormInstance;
+ formName: string;
+ disabled?: boolean;
+ initialValues?: AccessFormVercelConfigFieldValues;
+ onValuesChange?: (values: AccessFormVercelConfigFieldValues) => void;
+};
+
+const initFormModel = (): AccessFormVercelConfigFieldValues => {
+ return {
+ apiAccessToken: "",
+ };
+};
+
+const AccessFormVercelConfig = ({ form: formInst, formName, disabled, initialValues, onValuesChange }: AccessFormVercelConfigProps) => {
+ const { t } = useTranslation();
+
+ const formSchema = z.object({
+ apiAccessToken: z
+ .string()
+ .min(1, t("access.form.vercel_api_access_token.placeholder"))
+ .max(256, t("common.errmsg.string_max", { max: 256 }))
+ .trim(),
+ teamId: z
+ .string()
+ .max(256, t("common.errmsg.string_max", { max: 256 }))
+ .trim()
+ .nullish(),
+ });
+ const formRule = createSchemaFieldRule(formSchema);
+
+ const handleFormChange = (_: unknown, values: z.infer) => {
+ onValuesChange?.(values);
+ };
+
+ return (
+ }
+ >
+
+
+
+ }
+ >
+
+
+
+ );
+};
+
+export default AccessFormVercelConfig;
diff --git a/ui/src/components/access/AccessFormVolcEngineConfig.tsx b/ui/src/components/access/AccessFormVolcEngineConfig.tsx
index 02a693b0..8d8d18de 100644
--- a/ui/src/components/access/AccessFormVolcEngineConfig.tsx
+++ b/ui/src/components/access/AccessFormVolcEngineConfig.tsx
@@ -28,9 +28,9 @@ const AccessFormVolcEngineConfig = ({ form: formInst, formName, disabled, initia
const formSchema = z.object({
accessKeyId: z
.string()
- .trim()
.min(1, t("access.form.volcengine_access_key_id.placeholder"))
- .max(64, t("common.errmsg.string_max", { max: 64 })),
+ .max(64, t("common.errmsg.string_max", { max: 64 }))
+ .trim(),
secretAccessKey: z
.string()
.min(1, t("access.form.volcengine_secret_access_key.placeholder"))
diff --git a/ui/src/components/access/AccessFormWestcnConfig.tsx b/ui/src/components/access/AccessFormWestcnConfig.tsx
index 0b0257ed..ab21260c 100644
--- a/ui/src/components/access/AccessFormWestcnConfig.tsx
+++ b/ui/src/components/access/AccessFormWestcnConfig.tsx
@@ -28,9 +28,9 @@ const AccessFormWestcnConfig = ({ form: formInst, formName, disabled, initialVal
const formSchema = z.object({
username: z
.string()
- .trim()
.min(1, t("access.form.westcn_username.placeholder"))
- .max(64, t("common.errmsg.string_max", { max: 64 })),
+ .max(64, t("common.errmsg.string_max", { max: 64 }))
+ .trim(),
apiPassword: z
.string()
.min(1, t("access.form.westcn_api_password.placeholder"))
diff --git a/ui/src/domain/access.ts b/ui/src/domain/access.ts
index 0bbab303..ed1bc1ee 100644
--- a/ui/src/domain/access.ts
+++ b/ui/src/domain/access.ts
@@ -44,6 +44,7 @@ export interface AccessModel extends BaseModel {
| AccessConfigForTencentCloud
| AccessConfigForUCloud
| AccessConfigForUpyun
+ | AccessConfigForVercel
| AccessConfigForVolcEngine
| AccessConfigForWebhook
| AccessConfigForWestcn
@@ -246,6 +247,11 @@ export type AccessConfigForUpyun = {
password: string;
};
+export type AccessConfigForVercel = {
+ apiAccessToken: string;
+ teamId?: string;
+};
+
export type AccessConfigForVolcEngine = {
accessKeyId: string;
secretAccessKey: string;
diff --git a/ui/src/domain/provider.ts b/ui/src/domain/provider.ts
index f26eba63..a0119bca 100644
--- a/ui/src/domain/provider.ts
+++ b/ui/src/domain/provider.ts
@@ -110,6 +110,7 @@ export const accessProvidersMap: Maphttps://console.ucloud-global.com/uaccount/iam/project_manage",
+ "access.form.vercel_api_access_token.label": "Vercel API access token",
+ "access.form.vercel_api_access_token.placeholder": "Please enter Vercel API access token",
+ "access.form.vercel_api_access_token.tooltip": "For more information, see https://vercel.com/guides/how-do-i-use-a-vercel-api-access-token",
+ "access.form.vercel_team_id.label": "Vercel team ID (Optional)",
+ "access.form.vercel_team_id.placeholder": "Please enter Vercel team ID",
+ "access.form.vercel_team_id.tooltip": "For more information, see https://vercel.com/docs/accounts#find-your-team-id",
"access.form.volcengine_access_key_id.label": "VolcEngine AccessKeyId",
"access.form.volcengine_access_key_id.placeholder": "Please enter VolcEngine AccessKeyId",
"access.form.volcengine_access_key_id.tooltip": "For more information, see https://www.volcengine.com/docs/6291/216571",
diff --git a/ui/src/i18n/locales/zh/nls.access.json b/ui/src/i18n/locales/zh/nls.access.json
index aa60d5e8..bec170b6 100644
--- a/ui/src/i18n/locales/zh/nls.access.json
+++ b/ui/src/i18n/locales/zh/nls.access.json
@@ -261,6 +261,12 @@
"access.form.upyun_password.label": "又拍云子账号密码",
"access.form.upyun_password.placeholder": "请输入又拍云子账号密码",
"access.form.upyun_password.tooltip": "这是什么?请参阅 https://console.upyun.com/account/subaccount/
请关闭该账号的二次登录验证。",
+ "access.form.vercel_api_access_token.label": "Vercel API Access Token",
+ "access.form.vercel_api_access_token.placeholder": "请输入 Vercel API Access Token",
+ "access.form.vercel_api_access_token.tooltip": "这是什么?请参阅 https://vercel.com/guides/how-do-i-use-a-vercel-api-access-token",
+ "access.form.vercel_team_id.label": "Vercel 团队 ID(可选)",
+ "access.form.vercel_team_id.placeholder": "请输入 Vercel 团队 ID",
+ "access.form.vercel_team_id.tooltip": "这是什么?请参阅 https://vercel.com/docs/accounts#find-your-team-id",
"access.form.volcengine_access_key_id.label": "火山引擎 AccessKeyId",
"access.form.volcengine_access_key_id.placeholder": "请输入火山引擎 AccessKeyId",
"access.form.volcengine_access_key_id.tooltip": "这是什么?请参阅 https://www.volcengine.com/docs/6291/216571",