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",