mirror of
https://github.com/certd/certd.git
synced 2026-08-10 17:25:49 +08:00
feat: 优化开放接口证书自动申请功能
1. 新增TextedException统一异常类 2. 新增AccessService获取默认授权方法 3. 扩展证书流水线批量更新字段,支持自动挑战类型、SSL提供商和ACME账户 4. 调整证书申请默认重试次数为1并优化重试逻辑 5. 优化证书模板获取逻辑,支持ID为0时获取默认模板 6. 完善自动流水线创建逻辑,默认设置挑战类型为auto 7. 调整默认证书链为ISRG Root X2 8. 优化自动申请证书的参数处理,补充邮箱、ACME账户和重试次数默认值 9. 替换部分异常抛出为TextedException
This commit is contained in:
@@ -21,3 +21,10 @@ export class TextException extends BaseException {
|
||||
super(name, code, message, data);
|
||||
}
|
||||
}
|
||||
|
||||
export class TextedException extends TextException {
|
||||
constructor(res: { code: number; message: string; data?: any; name?: string }) {
|
||||
const { code, message, data, name } = res;
|
||||
super(name || "TextedException", code, message, data);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -289,4 +289,15 @@ export class AccessService extends BaseService<AccessEntity> {
|
||||
await this.repository.save(newAccess);
|
||||
return newAccess.id;
|
||||
}
|
||||
|
||||
async getDefaultByType({ type, userId, projectId, subtype }) {
|
||||
return await this.repository.findOne({
|
||||
where: {
|
||||
type,
|
||||
userId,
|
||||
projectId,
|
||||
subtype,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
+13
-3
@@ -18,7 +18,7 @@ const emit = defineEmits<{
|
||||
change: any;
|
||||
}>();
|
||||
|
||||
const batchUpdateFields = ["renewDays", "privateKeyType"];
|
||||
const batchUpdateFields = ["challengeType", "sslProvider", "acmeAccountAccessId", "renewDays", "privateKeyType"];
|
||||
|
||||
function hasFormValue(form: any, field: string) {
|
||||
return form[field] != null && form[field] !== "";
|
||||
@@ -44,12 +44,18 @@ const { openCrudFormDialog } = useFormWrapper();
|
||||
const settingStore = useSettingStore();
|
||||
const pluginStore = usePluginStore();
|
||||
|
||||
function createInputColumn(inputDefine: any) {
|
||||
function createInputColumn(field: string, inputDefine: any) {
|
||||
const form = cloneDeep(inputDefine);
|
||||
useReference(form);
|
||||
delete form.value;
|
||||
delete form.rules;
|
||||
form.required = false;
|
||||
if (field === "challengeType") {
|
||||
form.component = {
|
||||
...form.component,
|
||||
options: [{ value: "auto", label: "自动匹配" }],
|
||||
};
|
||||
}
|
||||
if (form.component) {
|
||||
form.component.allowClear = true;
|
||||
}
|
||||
@@ -62,7 +68,11 @@ function createInputColumn(inputDefine: any) {
|
||||
function createColumns(inputDefines: any) {
|
||||
const columns: any = {};
|
||||
for (const field of batchUpdateFields) {
|
||||
columns[field] = createInputColumn(inputDefines[field]);
|
||||
const inputDefine = inputDefines[field];
|
||||
if (inputDefine == null) {
|
||||
continue;
|
||||
}
|
||||
columns[field] = createInputColumn(field, inputDefine);
|
||||
}
|
||||
return columns;
|
||||
}
|
||||
|
||||
@@ -74,7 +74,7 @@ export class CertApplyTemplateService extends BaseService<CertApplyTemplateEntit
|
||||
}
|
||||
|
||||
private async getTemplateParams(req: ResolveApplyTemplateReq) {
|
||||
if (!req.templateId) {
|
||||
if (req.templateId == null) {
|
||||
return {};
|
||||
}
|
||||
const template = await this.getTemplateById(req.templateId, req.userId, req.projectId);
|
||||
@@ -85,6 +85,10 @@ export class CertApplyTemplateService extends BaseService<CertApplyTemplateEntit
|
||||
}
|
||||
|
||||
private async getTemplateById(id: number, userId: number, projectId?: number) {
|
||||
if (id === 0) {
|
||||
//获取默认模版
|
||||
return await this.getDefault(userId, projectId);
|
||||
}
|
||||
const userProjectQuery = this.buildUserProjectQuery(userId, projectId);
|
||||
const template = await this.repository.findOne({
|
||||
where: {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Inject, Provide, Scope, ScopeEnum } from "@midwayjs/core";
|
||||
import { CodeException, Constants } from "@certd/lib-server";
|
||||
import { AccessService, CodeException, Constants, TextedException } from "@certd/lib-server";
|
||||
import { CertInfoEntity } from "../entity/cert-info.js";
|
||||
import { utils } from "@certd/basic";
|
||||
import { PipelineService } from "../../pipeline/service/pipeline-service.js";
|
||||
@@ -30,6 +30,9 @@ export class CertInfoFacade {
|
||||
@Inject()
|
||||
certApplyTemplateService: CertApplyTemplateService;
|
||||
|
||||
@Inject()
|
||||
accessService: AccessService;
|
||||
|
||||
async getCertInfo(req: { domains?: string; certId?: number; userId: number; projectId: number; autoApply?: boolean; format?: string; autoApplyTemplateId?: number; autoApplyParams?: CertApplyTemplateParams }) {
|
||||
const { domains, certId, userId, projectId } = req;
|
||||
if (certId) {
|
||||
@@ -129,11 +132,6 @@ export class CertInfoFacade {
|
||||
}
|
||||
}
|
||||
|
||||
const userEmailSetting = await this.userSettingsService.getSetting<UserEmailSetting>(req.userId, null, UserEmailSetting);
|
||||
if (!userEmailSetting.list) {
|
||||
throw new CodeException(Constants.res.openEmailNotFound);
|
||||
}
|
||||
const email = userEmailSetting.list[0];
|
||||
const applyParams = await this.certApplyTemplateService.resolveApplyParams({
|
||||
userId: req.userId,
|
||||
projectId: req.projectId,
|
||||
@@ -141,9 +139,30 @@ export class CertInfoFacade {
|
||||
params: req.autoApplyParams,
|
||||
});
|
||||
|
||||
if (!applyParams.email) {
|
||||
let email = null;
|
||||
const userEmailSetting = await this.userSettingsService.getSetting<UserEmailSetting>(req.userId, null, UserEmailSetting);
|
||||
if (userEmailSetting.list && userEmailSetting.list.length > 0) {
|
||||
email = userEmailSetting.list[0];
|
||||
applyParams.email = email;
|
||||
}
|
||||
}
|
||||
|
||||
if (!applyParams.acmeAccountAccessId) {
|
||||
//如果没有配置acmeAccountAccessId,则默认使用默认的acmeAccountAccessId
|
||||
applyParams.sslProvider = "letsencrypt";
|
||||
const defaultAccount = await this.accessService.getDefaultByType({type:"acmeAccount",userId:req.userId,projectId:req.projectId,subtype:"letsencrypt"});
|
||||
if (defaultAccount) {
|
||||
applyParams.acmeAccountAccessId = defaultAccount.id;
|
||||
}
|
||||
}
|
||||
if (applyParams.certApplyRetryCount == null) {
|
||||
//如果没有配置重试次数,则默认再重试2次
|
||||
applyParams.certApplyRetryCount = 2;
|
||||
}
|
||||
|
||||
return await this.pipelineService.createAutoPipeline({
|
||||
domains: req.domains,
|
||||
email,
|
||||
projectId: req.projectId,
|
||||
userId: req.userId,
|
||||
from: "OpenAPI",
|
||||
@@ -168,7 +187,7 @@ export class CertInfoFacade {
|
||||
await utils.sleep(2000);
|
||||
}
|
||||
const certInfo = await this.certInfoService.getByPipelineId(req.pipelineId);
|
||||
throw new CodeException({
|
||||
throw new TextedException({
|
||||
...Constants.res.openCertApplying,
|
||||
data: {
|
||||
pipelineId: req.pipelineId,
|
||||
@@ -176,7 +195,6 @@ export class CertInfoFacade {
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
private canRetryErrorPipeline(updateTime?: Date) {
|
||||
if (!updateTime) {
|
||||
return false;
|
||||
|
||||
@@ -79,6 +79,44 @@ describe("pipeline batch update", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("updates automatic domain verification, issuer and ACME account", () => {
|
||||
const pipeline: any = {
|
||||
stages: [
|
||||
{
|
||||
tasks: [
|
||||
{
|
||||
steps: [
|
||||
{
|
||||
type: "CertApply",
|
||||
input: {
|
||||
challengeType: "dns",
|
||||
sslProvider: "letsencrypt",
|
||||
acmeAccountAccessId: 1,
|
||||
renewDays: 20,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
updateCertApplyStepInputs(pipeline, {
|
||||
challengeType: "auto",
|
||||
sslProvider: "google",
|
||||
acmeAccountAccessId: 2,
|
||||
renewDays: 10,
|
||||
});
|
||||
|
||||
assert.deepEqual(pipeline.stages[0].tasks[0].steps[0].input, {
|
||||
challengeType: "auto",
|
||||
sslProvider: "google",
|
||||
acmeAccountAccessId: 2,
|
||||
renewDays: 10,
|
||||
});
|
||||
});
|
||||
|
||||
it("updates uploaded cert pipelines only for fields defined by the plugin", () => {
|
||||
const pipeline: any = {
|
||||
stages: [
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
export type CertApplyStepInputPatch = {
|
||||
challengeType?: "auto";
|
||||
sslProvider?: string;
|
||||
acmeAccountAccessId?: number;
|
||||
renewDays?: number;
|
||||
privateKeyType?: string;
|
||||
};
|
||||
@@ -30,7 +33,7 @@ function applyPatchFields(target: Record<string, unknown>, patch: CertApplyStepI
|
||||
}
|
||||
|
||||
export function updateCertApplyStepInputs(pipeline: any, patch: CertApplyStepInputPatch, getStepInputDefine?: GetStepInputDefine) {
|
||||
const fields: (keyof CertApplyStepInputPatch)[] = ["renewDays", "privateKeyType"];
|
||||
const fields: (keyof CertApplyStepInputPatch)[] = ["challengeType", "sslProvider", "acmeAccountAccessId", "renewDays", "privateKeyType"];
|
||||
let count = 0;
|
||||
for (const stage of pipeline?.stages || []) {
|
||||
for (const task of stage?.tasks || []) {
|
||||
|
||||
@@ -1303,11 +1303,18 @@ export class PipelineService extends BaseService<PipelineEntity> {
|
||||
}
|
||||
}
|
||||
|
||||
async createAutoPipeline(req: { domains: string[]; email: string; userId: number; projectId?: number; from: string; applyParams?: CertApplyTemplateParams }) {
|
||||
async createAutoPipeline(req: { domains: string[]; userId: number; projectId?: number; from: string; applyParams?: CertApplyTemplateParams }) {
|
||||
const randomHour = Math.floor(Math.random() * 6);
|
||||
const randomMin = Math.floor(Math.random() * 60);
|
||||
const randomCron = `0 ${randomMin} ${randomHour} * * *`;
|
||||
|
||||
const applyParams:any = {
|
||||
...req.applyParams,
|
||||
domains: req.domains,
|
||||
}
|
||||
if (!applyParams.challengeType) {
|
||||
applyParams.challengeType = "auto";
|
||||
}
|
||||
const pipeline: any = {
|
||||
title: req.domains[0] + `证书自动申请【${req.from ?? "OpenAPI"}】`,
|
||||
runnableType: "pipeline",
|
||||
@@ -1351,17 +1358,14 @@ export class PipelineService extends BaseService<PipelineEntity> {
|
||||
sslProvider: "letsencrypt",
|
||||
privateKeyType: "rsa_2048",
|
||||
certProfile: "classic",
|
||||
preferredChain: "ISRG Root X1",
|
||||
preferredChain: "ISRG Root X2",
|
||||
useProxy: false,
|
||||
skipLocalVerify: false,
|
||||
maxCheckRetryCount: 20,
|
||||
waitDnsDiffuseTime: 30,
|
||||
pfxArgs: "-macalg SHA1 -keypbe PBE-SHA1-3DES -certpbe PBE-SHA1-3DES",
|
||||
successNotify: true,
|
||||
...req.applyParams,
|
||||
domains: req.domains,
|
||||
email: req.email,
|
||||
challengeType: "auto",
|
||||
...applyParams,
|
||||
},
|
||||
strategy: {
|
||||
runStrategy: 0, // 正常执行
|
||||
|
||||
@@ -555,7 +555,7 @@ export class CertApplyPlugin extends CertApplyBasePlugin {
|
||||
|
||||
@TaskInput({
|
||||
title: "证书申请失败重试次数",
|
||||
value: 0,
|
||||
value: 1,
|
||||
component: {
|
||||
name: "a-input-number",
|
||||
vModel: "value",
|
||||
@@ -565,7 +565,7 @@ export class CertApplyPlugin extends CertApplyBasePlugin {
|
||||
maybeNeed: true,
|
||||
helper: "证书申请失败后,等待30秒再自动重试;0表示不重试",
|
||||
})
|
||||
certApplyRetryCount = 0;
|
||||
certApplyRetryCount?: number;
|
||||
|
||||
acme!: AcmeService;
|
||||
|
||||
@@ -706,7 +706,7 @@ export class CertApplyPlugin extends CertApplyBasePlugin {
|
||||
}
|
||||
|
||||
private getCertApplyRetryCount() {
|
||||
const retryCount = Number(this.certApplyRetryCount);
|
||||
const retryCount = Number(this.certApplyRetryCount || 1);
|
||||
if (!Number.isFinite(retryCount) || retryCount <= 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user