mirror of
https://github.com/certd/certd.git
synced 2026-05-13 19:47:55 +08:00
perf: 重构自动加载模块并优化EAB授权处理
refactor(ui): 将分散的auto-*模块整合为统一命名的auto-register模块 perf(plugin-cert): 增强EAB授权功能,支持账号私钥刷新和类型选择 test: 添加EAB授权服务和ACME账号配置的单元测试 docs: 更新AGENTS.md补充ACME/EAB使用注意事项 chore: 统一各package.json中的测试脚本配置
This commit is contained in:
@@ -142,6 +142,12 @@ Certd 是一个支持私有化部署的 SSL/TLS 证书自动化管理平台。
|
||||
|
||||
如果只是某个服务商或部署目标的问题,不要轻易修改共享 pipeline/core 行为,除非确实是可复用的公共能力。
|
||||
|
||||
### ACME / EAB 注意事项
|
||||
|
||||
- 公共 EAB(尤其是 Google EAB)可能只能创建一次 ACME 账号。要跨用户复用公共 EAB,应保存并复用同一个 ACME account private key;`accountUrl` 如果存到 `userContext` 里,只能视为当前用户缓存,因为 `userContext` 跟用户 id 走。
|
||||
- ACME 协议的 `newAccount` 支持 `onlyReturnExisting`。使用同一个 account private key 调用 `newAccount({ onlyReturnExisting: true })` 可以取回已创建账号的 URL,且不会再次消费 EAB。
|
||||
- 修改 EAB 的 `kid` 后,应重新生成绑定该 `kid` 的 account private key;否则应阻止继续申请并提示用户刷新账号私钥。
|
||||
|
||||
## 数据与迁移
|
||||
|
||||
后端使用 TypeORM 实体加 SQL 迁移。
|
||||
@@ -161,6 +167,7 @@ Certd 是一个支持私有化部署的 SSL/TLS 证书自动化管理平台。
|
||||
- 优先沿用现有模块、插件、服务模式,再考虑新增抽象。
|
||||
- `packages/ui/certd-server/data/`、`logs/`、生成的 metadata/dist 等通常视为运行时或构建产物,除非任务明确要求处理它们。
|
||||
- 注意本地数据和配置里可能包含凭据、证书材料等敏感信息。
|
||||
- 本仓库代码注释优先使用中文,尤其是解释业务规则、兼容逻辑、协议细节和隐藏风险时;除非文件已有明确英文注释风格或引用外部英文术语,否则不要新增英文说明性注释。
|
||||
|
||||
## 插件开发技能
|
||||
|
||||
|
||||
@@ -35,10 +35,12 @@
|
||||
"@typescript-eslint/parser": "^8.26.1",
|
||||
"chai": "^4.4.1",
|
||||
"chai-as-promised": "^7.1.2",
|
||||
"cross-env": "^7.0.3",
|
||||
"eslint": "^8.57.0",
|
||||
"eslint-config-prettier": "^8.5.0",
|
||||
"eslint-plugin-import": "^2.29.1",
|
||||
"eslint-plugin-prettier": "^4.2.1",
|
||||
"esmock": "^2.7.5",
|
||||
"jsdoc-to-markdown": "^8.0.1",
|
||||
"mocha": "^10.6.0",
|
||||
"nock": "^13.5.4",
|
||||
@@ -55,7 +57,7 @@
|
||||
"prepublishOnly": "npm run build && npm run build-docs",
|
||||
"test": "mocha -t 60000 \"test/setup.js\" \"test/**/*.spec.js\"",
|
||||
"before-test:unit": "node -e \"const fs=require('fs');fs.rmSync('dist-test',{recursive:true,force:true});fs.rmSync('tsconfig.test.tsbuildinfo',{force:true});\"",
|
||||
"test:unit": "npm run before-test:unit && tsc -p tsconfig.test.json --skipLibCheck && mocha -t 60000 \"dist-test/**/*.test.js\"",
|
||||
"test:unit": "cross-env NODE_ENV=unittest npm run before-test:unit && cross-env NODE_ENV=unittest tsc -p tsconfig.test.json --skipLibCheck && cross-env NODE_ENV=unittest mocha -t 60000 \"dist-test/**/*.test.js\"",
|
||||
"pub": "npm publish",
|
||||
"compile": "tsc --skipLibCheck --watch"
|
||||
},
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
"dev-build": "npm run build",
|
||||
"preview": "vite preview",
|
||||
"test": "mocha --loader=ts-node/esm",
|
||||
"test:unit": "mocha --node-option no-warnings --node-option loader=ts-node/esm \"src/**/*.test.ts\"",
|
||||
"test:unit": "cross-env NODE_ENV=unittest mocha --node-option no-warnings --node-option loader=ts-node/esm \"src/**/*.test.ts\"",
|
||||
"pub": "npm publish",
|
||||
"compile": "tsc --skipLibCheck --watch"
|
||||
},
|
||||
@@ -40,9 +40,11 @@
|
||||
"@typescript-eslint/eslint-plugin": "^8.26.1",
|
||||
"@typescript-eslint/parser": "^8.26.1",
|
||||
"chai": "4.3.10",
|
||||
"cross-env": "^7.0.3",
|
||||
"eslint": "^8.41.0",
|
||||
"eslint-config-prettier": "^8.8.0",
|
||||
"eslint-plugin-prettier": "^4.2.1",
|
||||
"esmock": "^2.7.5",
|
||||
"mocha": "^10.2.0",
|
||||
"prettier": "^2.8.8",
|
||||
"rimraf": "^5.0.5",
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
"build3": "rollup -c",
|
||||
"preview": "vite preview",
|
||||
"test": "mocha --loader=ts-node/esm",
|
||||
"test:unit": "mocha --no-config --node-option no-warnings --node-option loader=ts-node/esm \"src/**/*.test.ts\"",
|
||||
"test:unit": "cross-env NODE_ENV=unittest mocha --no-config --node-option no-warnings --node-option loader=ts-node/esm \"src/**/*.test.ts\"",
|
||||
"pub": "npm publish",
|
||||
"compile": "tsc --skipLibCheck --watch"
|
||||
},
|
||||
@@ -37,9 +37,11 @@
|
||||
"@typescript-eslint/eslint-plugin": "^8.26.1",
|
||||
"@typescript-eslint/parser": "^8.26.1",
|
||||
"chai": "4.3.10",
|
||||
"cross-env": "^7.0.3",
|
||||
"eslint": "^8.41.0",
|
||||
"eslint-config-prettier": "^8.8.0",
|
||||
"eslint-plugin-prettier": "^4.2.1",
|
||||
"esmock": "^2.7.5",
|
||||
"mocha": "^10.2.0",
|
||||
"prettier": "^2.8.8",
|
||||
"rimraf": "^5.0.5",
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
"build": "npm run before-build && rollup -c ",
|
||||
"dev-build": "npm run build",
|
||||
"preview": "vite preview",
|
||||
"test:unit": "echo no unit tests",
|
||||
"test:unit": "cross-env NODE_ENV=unittest echo no unit tests",
|
||||
"pub": "npm publish"
|
||||
},
|
||||
"dependencies": {
|
||||
@@ -22,6 +22,8 @@
|
||||
"devDependencies": {
|
||||
"@typescript-eslint/eslint-plugin": "^8.26.1",
|
||||
"@typescript-eslint/parser": "^8.26.1",
|
||||
"cross-env": "^7.0.3",
|
||||
"esmock": "^2.7.5",
|
||||
"prettier": "^2.8.8",
|
||||
"tslib": "^2.8.1"
|
||||
},
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
"build3": "rollup -c",
|
||||
"build2": "vue-tsc --noEmit && vite build",
|
||||
"preview": "vite preview",
|
||||
"test:unit": "echo no unit tests",
|
||||
"test:unit": "cross-env NODE_ENV=unittest echo no unit tests",
|
||||
"pub": "npm publish"
|
||||
},
|
||||
"dependencies": {
|
||||
@@ -24,9 +24,11 @@
|
||||
"@types/chai": "^4.3.3",
|
||||
"@typescript-eslint/eslint-plugin": "^8.26.1",
|
||||
"@typescript-eslint/parser": "^8.26.1",
|
||||
"cross-env": "^7.0.3",
|
||||
"eslint": "^8.24.0",
|
||||
"eslint-config-prettier": "^8.5.0",
|
||||
"eslint-plugin-prettier": "^4.2.1",
|
||||
"esmock": "^2.7.5",
|
||||
"prettier": "^2.8.8",
|
||||
"rimraf": "^5.0.5",
|
||||
"tslib": "^2.8.1",
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
"scripts": {
|
||||
"build": "rollup -c ",
|
||||
"dev-build": "npm run build",
|
||||
"test:unit": "echo no unit tests",
|
||||
"test:unit": "cross-env NODE_ENV=unittest echo no unit tests",
|
||||
"pub": "npm publish"
|
||||
},
|
||||
"author": "",
|
||||
@@ -30,7 +30,8 @@
|
||||
"@typescript-eslint/parser": "^8.26.1",
|
||||
"chai": "^4.1.2",
|
||||
"config": "^1.30.0",
|
||||
"cross-env": "^5.1.4",
|
||||
"cross-env": "^7.0.3",
|
||||
"esmock": "^2.7.5",
|
||||
"js-yaml": "^3.11.0",
|
||||
"mocha": "^5.0.0",
|
||||
"prettier": "^2.8.8",
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
"build3": "rollup -c",
|
||||
"build2": "vue-tsc --noEmit && vite build",
|
||||
"preview": "vite preview",
|
||||
"test:unit": "echo no unit tests",
|
||||
"test:unit": "cross-env NODE_ENV=unittest echo no unit tests",
|
||||
"pub": "npm publish",
|
||||
"compile": "tsc --skipLibCheck --watch"
|
||||
},
|
||||
@@ -26,9 +26,11 @@
|
||||
"@types/chai": "^4.3.3",
|
||||
"@typescript-eslint/eslint-plugin": "^8.26.1",
|
||||
"@typescript-eslint/parser": "^8.26.1",
|
||||
"cross-env": "^7.0.3",
|
||||
"eslint": "^8.24.0",
|
||||
"eslint-config-prettier": "^8.5.0",
|
||||
"eslint-plugin-prettier": "^4.2.1",
|
||||
"esmock": "^2.7.5",
|
||||
"prettier": "^2.8.8",
|
||||
"rimraf": "^5.0.5",
|
||||
"tslib": "^2.8.1",
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
"build": "npm run before-build && tsc --skipLibCheck",
|
||||
"dev-build": "npm run build",
|
||||
"test": "midway-bin test --ts -V",
|
||||
"test:unit": "mocha --no-config --node-option no-warnings --node-option loader=ts-node/esm \"src/**/*.test.ts\"",
|
||||
"test:unit": "cross-env NODE_ENV=unittest mocha --no-config --node-option no-warnings --node-option loader=ts-node/esm \"src/**/*.test.ts\"",
|
||||
"test1": "midway-bin test --ts -V -f test/blank.test.ts -t 'hash-check'",
|
||||
"cov": "midway-bin cov --ts",
|
||||
"lint": "mwts check",
|
||||
@@ -44,7 +44,6 @@
|
||||
"@midwayjs/upload": "3.20.13",
|
||||
"@midwayjs/validate": "3.20.13",
|
||||
"better-sqlite3": "^11.1.2",
|
||||
"cross-env": "^7.0.3",
|
||||
"dayjs": "^1.11.7",
|
||||
"lodash-es": "^4.17.21",
|
||||
"mwts": "^1.3.0",
|
||||
@@ -57,9 +56,11 @@
|
||||
"@types/node": "^18",
|
||||
"@typescript-eslint/eslint-plugin": "^8.26.1",
|
||||
"@typescript-eslint/parser": "^8.26.1",
|
||||
"cross-env": "^7.0.3",
|
||||
"eslint": "^8.24.0",
|
||||
"eslint-config-prettier": "^8.5.0",
|
||||
"eslint-plugin-prettier": "^4.2.1",
|
||||
"esmock": "^2.7.5",
|
||||
"mocha": "^10.2.0",
|
||||
"prettier": "^2.8.8",
|
||||
"rimraf": "^5.0.5",
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
import assert from "assert";
|
||||
import { AccessService } from "./access-service.js";
|
||||
|
||||
describe("AccessService", () => {
|
||||
it("does not write id into access setting when updating selected fields", async () => {
|
||||
let updateParam: any;
|
||||
const service = new AccessService();
|
||||
service.info = async () => ({
|
||||
id: 12,
|
||||
type: "eab",
|
||||
} as any);
|
||||
service.decryptAccessEntity = () => ({
|
||||
kid: "kid-1",
|
||||
});
|
||||
service.update = async (param: any) => {
|
||||
updateParam = param;
|
||||
return param;
|
||||
};
|
||||
|
||||
await service.updateAccess({
|
||||
id: 12,
|
||||
accountKey: "account-key",
|
||||
});
|
||||
|
||||
assert.deepEqual(JSON.parse(updateParam.setting), {
|
||||
kid: "kid-1",
|
||||
accountKey: "account-key",
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -123,6 +123,25 @@ export class AccessService extends BaseService<AccessEntity> {
|
||||
return await super.update(param);
|
||||
}
|
||||
|
||||
async updateAccess(access: any) {
|
||||
const oldEntity = await this.info(access.id);
|
||||
if (oldEntity == null) {
|
||||
throw new ValidateException('该授权配置不存在,请确认是否已被删除');
|
||||
}
|
||||
const setting = this.decryptAccessEntity(oldEntity);
|
||||
for (const key of Object.keys(access)) {
|
||||
if (key === 'id') {
|
||||
continue;
|
||||
}
|
||||
setting[key] = access[key];
|
||||
}
|
||||
return await this.update({
|
||||
id: access.id,
|
||||
type: oldEntity.type,
|
||||
setting: JSON.stringify(setting),
|
||||
});
|
||||
}
|
||||
|
||||
async getSimpleInfo(id: number) {
|
||||
const entity = await this.info(id);
|
||||
if (entity == null) {
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
"dev-build": "npm run build",
|
||||
"test": "midway-bin test --ts -V",
|
||||
"test1": "midway-bin test --ts -V -f test/blank.test.ts -t 'hash-check'",
|
||||
"test:unit": "echo no unit tests",
|
||||
"test:unit": "cross-env NODE_ENV=unittest echo no unit tests",
|
||||
"cov": "midway-bin cov --ts",
|
||||
"prepublish": "npm run build",
|
||||
"pub": "npm publish"
|
||||
@@ -36,11 +36,13 @@
|
||||
"@types/node": "^18",
|
||||
"@typescript-eslint/eslint-plugin": "^8.26.1",
|
||||
"@typescript-eslint/parser": "^8.26.1",
|
||||
"cross-env": "^7.0.3",
|
||||
"eslint": "^8.24.0",
|
||||
"eslint-config-prettier": "^8.5.0",
|
||||
"eslint-plugin-import": "^2.26.0",
|
||||
"eslint-plugin-node": "^11.1.0",
|
||||
"eslint-plugin-prettier": "^4.2.1",
|
||||
"esmock": "^2.7.5",
|
||||
"prettier": "^2.8.8",
|
||||
"rimraf": "^5.0.5",
|
||||
"tslib": "^2.8.1",
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
"build3": "rollup -c",
|
||||
"build2": "vue-tsc --noEmit && vite build",
|
||||
"preview": "vite preview",
|
||||
"test:unit": "echo no unit tests",
|
||||
"test:unit": "cross-env NODE_ENV=unittest echo no unit tests",
|
||||
"pub": "npm publish",
|
||||
"compile": "tsc --skipLibCheck --watch"
|
||||
},
|
||||
@@ -31,9 +31,11 @@
|
||||
"@typescript-eslint/eslint-plugin": "^8.26.1",
|
||||
"@typescript-eslint/parser": "^8.26.1",
|
||||
"chai": "^4.3.6",
|
||||
"cross-env": "^7.0.3",
|
||||
"eslint": "^8.24.0",
|
||||
"eslint-config-prettier": "^8.5.0",
|
||||
"eslint-plugin-prettier": "^4.2.1",
|
||||
"esmock": "^2.7.5",
|
||||
"mocha": "^10.1.0",
|
||||
"prettier": "^2.8.8",
|
||||
"tslib": "^2.8.1",
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
"build3": "rollup -c",
|
||||
"build2": "vue-tsc --noEmit && vite build",
|
||||
"preview": "vite preview",
|
||||
"test:unit": "mocha --no-config --node-option no-warnings --node-option loader=ts-node/esm \"src/**/*.test.ts\"",
|
||||
"test:unit": "cross-env NODE_ENV=unittest mocha --no-config --node-option no-warnings --node-option loader=ts-node/esm \"src/**/*.test.ts\"",
|
||||
"pub": "npm publish",
|
||||
"compile": "tsc --skipLibCheck --watch"
|
||||
},
|
||||
@@ -50,9 +50,11 @@
|
||||
"@typescript-eslint/eslint-plugin": "^8.26.1",
|
||||
"@typescript-eslint/parser": "^8.26.1",
|
||||
"chai": "^4.3.6",
|
||||
"cross-env": "^7.0.3",
|
||||
"eslint": "^8.24.0",
|
||||
"eslint-config-prettier": "^8.5.0",
|
||||
"eslint-plugin-prettier": "^4.2.1",
|
||||
"esmock": "^2.7.5",
|
||||
"mocha": "^10.1.0",
|
||||
"prettier": "^2.8.8",
|
||||
"ts-node": "^10.9.2",
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
"debug:force": "vite --force --mode debug",
|
||||
"build": "cross-env NODE_OPTIONS=--max-old-space-size=40960 vite build ",
|
||||
"dev-build": "echo 1",
|
||||
"test:unit": "echo no unit tests",
|
||||
"test:unit": "cross-env NODE_ENV=unittest echo no unit tests",
|
||||
"test:vue": "vitest run",
|
||||
"serve": "vite preview",
|
||||
"preview": "vite preview",
|
||||
@@ -62,7 +62,6 @@
|
||||
"cos-js-sdk-v5": "^1.7.0",
|
||||
"cron-parser": "^4.9.0",
|
||||
"cropperjs": "^1.6.1",
|
||||
"cross-env": "^7.0.3",
|
||||
"cssnano": "^7.0.6",
|
||||
"dayjs": "^1.11.7",
|
||||
"defu": "^6.1.4",
|
||||
@@ -127,6 +126,7 @@
|
||||
"autoprefixer": "^10.4.21",
|
||||
"caller-path": "^4.0.0",
|
||||
"chai": "^5.1.0",
|
||||
"cross-env": "^7.0.3",
|
||||
"dependency-cruiser": "^16.2.3",
|
||||
"dot": "^1.1.3",
|
||||
"eslint": "8.57.0",
|
||||
@@ -136,6 +136,7 @@
|
||||
"eslint-plugin-prettier": "^5.1.3",
|
||||
"eslint-plugin-promise": "^6.1.1",
|
||||
"eslint-plugin-vue": "^9.23.0",
|
||||
"esmock": "^2.7.5",
|
||||
"less": "^4.2.0",
|
||||
"less-loader": "^12.2.0",
|
||||
"postcss": "^8.4.35",
|
||||
|
||||
@@ -0,0 +1,106 @@
|
||||
<template>
|
||||
<div class="refresh-input">
|
||||
<div class="refresh-input-line">
|
||||
<a-input class="refresh-input-control" :value="value" :placeholder="placeholder" allow-clear @update:value="emit('update:value', $event)"></a-input>
|
||||
<fs-button :loading="loading" type="primary" :text="buttonText" :icon="icon" @click="doRefresh"></fs-button>
|
||||
</div>
|
||||
<div class="helper" :class="{ error: hasError }">
|
||||
{{ message }}
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ComponentPropsType, doRequest } from "/@/components/plugins/lib";
|
||||
import { computed, inject, ref } from "vue";
|
||||
import { Form } from "ant-design-vue";
|
||||
import { getInputFromForm } from "./utils";
|
||||
|
||||
defineOptions({
|
||||
name: "RefreshInput",
|
||||
});
|
||||
|
||||
type RefreshInputProps = ComponentPropsType & {
|
||||
buttonText?: string;
|
||||
icon?: string;
|
||||
placeholder?: string;
|
||||
successMessage?: string;
|
||||
};
|
||||
|
||||
const fromType: any = inject("getFromType");
|
||||
const getScope: any = inject("get:scope");
|
||||
const getPluginType: any = inject("get:plugin:type", () => {
|
||||
return "access";
|
||||
});
|
||||
const formItemContext = Form.useInjectFormItemContext();
|
||||
const props = defineProps<RefreshInputProps>();
|
||||
const emit = defineEmits<{
|
||||
"update:value": [value: string];
|
||||
}>();
|
||||
|
||||
const loading = ref(false);
|
||||
const message = ref("");
|
||||
const hasError = ref(false);
|
||||
|
||||
const action = computed(() => props.action);
|
||||
const buttonText = computed(() => props.buttonText || "刷新");
|
||||
const icon = computed(() => props.icon || "ion:refresh-outline");
|
||||
const placeholder = computed(() => props.placeholder || "");
|
||||
const successMessage = computed(() => props.successMessage || "刷新成功,请保存配置");
|
||||
|
||||
const doRefresh = async () => {
|
||||
if (loading.value) {
|
||||
return;
|
||||
}
|
||||
if (!action.value) {
|
||||
hasError.value = true;
|
||||
message.value = "缺少刷新动作配置";
|
||||
return;
|
||||
}
|
||||
|
||||
formItemContext.onFieldChange();
|
||||
|
||||
const { form } = getScope();
|
||||
const pluginType = getPluginType();
|
||||
const { input, record } = getInputFromForm(form, pluginType);
|
||||
|
||||
loading.value = true;
|
||||
message.value = "";
|
||||
hasError.value = false;
|
||||
try {
|
||||
const res = await doRequest(
|
||||
{
|
||||
type: pluginType,
|
||||
typeName: form.type,
|
||||
action: action.value,
|
||||
input,
|
||||
record,
|
||||
fromType,
|
||||
},
|
||||
{
|
||||
onError(err: any) {
|
||||
hasError.value = true;
|
||||
message.value = err.message;
|
||||
},
|
||||
showErrorNotify: false,
|
||||
}
|
||||
);
|
||||
emit("update:value", res);
|
||||
message.value = successMessage.value;
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.refresh-input-line {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.refresh-input-control {
|
||||
flex: 1;
|
||||
}
|
||||
</style>
|
||||
@@ -12,6 +12,7 @@ import AccessSelector from "/@/views/certd/access/access-selector/index.vue";
|
||||
import InputPassword from "./common/input-password.vue";
|
||||
import CertInfoUpdater from "/@/views/certd/pipeline/cert-upload/index.vue";
|
||||
import ApiTest from "./common/api-test.vue";
|
||||
import RefreshInput from "./common/refresh-input.vue";
|
||||
import ParamsShow from "./common/params-show.vue";
|
||||
export * from "./cert/index.js";
|
||||
export default {
|
||||
@@ -23,6 +24,7 @@ export default {
|
||||
app.component("CertInfoUpdater", CertInfoUpdater);
|
||||
|
||||
app.component("ApiTest", ApiTest);
|
||||
app.component("RefreshInput", RefreshInput);
|
||||
|
||||
app.component("SynologyDeviceIdGetter", SynologyIdDeviceGetter);
|
||||
app.component("RemoteAutoComplete", RemoteAutoComplete);
|
||||
|
||||
@@ -101,7 +101,6 @@
|
||||
"cache-manager": "^6.1.0",
|
||||
"cos-nodejs-sdk-v5": "^2.14.6",
|
||||
"cron-parser": "^4.9.0",
|
||||
"cross-env": "^7.0.3",
|
||||
"crypto-js": "^4.2.0",
|
||||
"dayjs": "^1.11.7",
|
||||
"esdk-obs-nodejs": "^3.25.6",
|
||||
@@ -159,6 +158,8 @@
|
||||
"@types/node": "^18",
|
||||
"@types/nodemailer": "^6.4.8",
|
||||
"c8": "^10.1.2",
|
||||
"cross-env": "^7.0.3",
|
||||
"esmock": "^2.7.5",
|
||||
"mocha": "^10.2.0",
|
||||
"prettier": "^2.8.8",
|
||||
"rimraf": "^5.0.5",
|
||||
|
||||
+3
-4
@@ -1,7 +1,7 @@
|
||||
import { logger } from '@certd/basic';
|
||||
import { SysSettingsService, SysSiteInfo } from '@certd/lib-server';
|
||||
import { getPlusInfo, isPlus } from "@certd/plus-core";
|
||||
import { Autoload, Config, Init, Inject, Scope, ScopeEnum } from '@midwayjs/core';
|
||||
import { Config, Inject, Provide, Scope, ScopeEnum } from '@midwayjs/core';
|
||||
import dayjs from "dayjs";
|
||||
import { Between } from "typeorm";
|
||||
import { DomainService } from '../cert/service/domain-service.js';
|
||||
@@ -14,9 +14,9 @@ import { PipelineService } from '../pipeline/service/pipeline-service.js';
|
||||
import { UserService } from "../sys/authority/service/user-service.js";
|
||||
import { ProjectService } from '../sys/enterprise/service/project-service.js';
|
||||
|
||||
@Autoload()
|
||||
@Provide()
|
||||
@Scope(ScopeEnum.Request, { allowDowngrade: true })
|
||||
export class AutoCRegisterCron {
|
||||
export class AutoCron {
|
||||
@Inject()
|
||||
pipelineService: PipelineService;
|
||||
|
||||
@@ -53,7 +53,6 @@ export class AutoCRegisterCron {
|
||||
|
||||
|
||||
|
||||
@Init()
|
||||
async init() {
|
||||
logger.info('加载定时trigger开始');
|
||||
await this.pipelineService.onStartup(this.immediateTriggerOnce, this.onlyAdminUser);
|
||||
@@ -0,0 +1,182 @@
|
||||
import assert from "assert";
|
||||
import esmock from "esmock";
|
||||
import { AutoFix, buildEabAccountKeyValue, buildLegacyGoogleAccountConfigWhere, parseStorageValue } from "./auto-fix.js";
|
||||
|
||||
function createAutoFix(options: { pluginConfigService?: any; accessService?: any; storageService?: any }) {
|
||||
const autoFix = new AutoFix();
|
||||
autoFix.pluginConfigService = options.pluginConfigService;
|
||||
autoFix.accessService = options.accessService;
|
||||
autoFix.storageService = options.storageService;
|
||||
return autoFix;
|
||||
}
|
||||
|
||||
describe("AutoFix", () => {
|
||||
it("parses legacy storage values", () => {
|
||||
const config = parseStorageValue(
|
||||
JSON.stringify({
|
||||
value: {
|
||||
key: "legacy-private-key",
|
||||
accountUrl: "https://example.com/acct/1",
|
||||
},
|
||||
})
|
||||
);
|
||||
|
||||
assert.equal(config.key, "legacy-private-key");
|
||||
});
|
||||
|
||||
it("builds the EAB account key payload", () => {
|
||||
const payload = JSON.parse(buildEabAccountKeyValue("kid-1", "private-key"));
|
||||
|
||||
assert.deepEqual(payload, {
|
||||
kid: "kid-1",
|
||||
privateKey: "private-key",
|
||||
});
|
||||
});
|
||||
|
||||
it("builds legacy Google account config query by exact email key only", () => {
|
||||
assert.deepEqual(buildLegacyGoogleAccountConfigWhere("user@example.com"), {
|
||||
userId: 1,
|
||||
scope: "user",
|
||||
namespace: "1",
|
||||
key: "acme.config.google.user@example.com",
|
||||
});
|
||||
});
|
||||
|
||||
it("finds legacy Google account config by exact email key only", async () => {
|
||||
let findOneWhere: any;
|
||||
let findCalled = false;
|
||||
const autoFix = createAutoFix({
|
||||
pluginConfigService: null as any,
|
||||
accessService: null as any,
|
||||
storageService: {
|
||||
getRepository() {
|
||||
return {
|
||||
async findOne(options: any) {
|
||||
findOneWhere = options.where;
|
||||
return {
|
||||
value: JSON.stringify({
|
||||
value: {
|
||||
privateKey: "legacy-private-key",
|
||||
},
|
||||
}),
|
||||
};
|
||||
},
|
||||
async find() {
|
||||
findCalled = true;
|
||||
return [];
|
||||
},
|
||||
};
|
||||
},
|
||||
} as any,
|
||||
});
|
||||
|
||||
const config = await autoFix.getLegacyGoogleAccountConfig("user@example.com");
|
||||
|
||||
assert.equal(config.privateKey, "legacy-private-key");
|
||||
assert.deepEqual(findOneWhere, buildLegacyGoogleAccountConfigWhere("user@example.com"));
|
||||
assert.equal(findCalled, false);
|
||||
});
|
||||
|
||||
it("does not query legacy Google account config without email", async () => {
|
||||
let repositoryCalled = false;
|
||||
const autoFix = createAutoFix({
|
||||
pluginConfigService: null as any,
|
||||
accessService: null as any,
|
||||
storageService: {
|
||||
getRepository() {
|
||||
repositoryCalled = true;
|
||||
return {};
|
||||
},
|
||||
} as any,
|
||||
});
|
||||
|
||||
const config = await autoFix.getLegacyGoogleAccountConfig();
|
||||
|
||||
assert.equal(config, null);
|
||||
assert.equal(repositoryCalled, false);
|
||||
});
|
||||
|
||||
it("skips Google common EAB account key fix outside commercial edition", async () => {
|
||||
let pluginConfigCalled = false;
|
||||
const autoFix = createAutoFix({
|
||||
pluginConfigService: {
|
||||
async getPluginConfig() {
|
||||
pluginConfigCalled = true;
|
||||
return null;
|
||||
},
|
||||
} as any,
|
||||
accessService: null as any,
|
||||
storageService: null as any,
|
||||
});
|
||||
|
||||
await autoFix.init();
|
||||
|
||||
assert.equal(pluginConfigCalled, false);
|
||||
});
|
||||
|
||||
it("fixes Google common EAB account key in commercial edition", async () => {
|
||||
const { AutoFix: MockedAutoFix } = await esmock("./auto-fix.js", {
|
||||
"@certd/plus-core": {
|
||||
isComm: () => true,
|
||||
},
|
||||
});
|
||||
let getAccessByIdArgs: any[] = [];
|
||||
let findOneWhere: any;
|
||||
let updateAccessParam: any;
|
||||
const autoFix = new MockedAutoFix();
|
||||
autoFix.pluginConfigService = {
|
||||
async getPluginConfig(options: any) {
|
||||
assert.deepEqual(options, {
|
||||
name: "CertApply",
|
||||
type: "builtIn",
|
||||
});
|
||||
return {
|
||||
sysSetting: {
|
||||
input: {
|
||||
googleCommonEabAccessId: 12,
|
||||
},
|
||||
},
|
||||
};
|
||||
},
|
||||
};
|
||||
autoFix.accessService = {
|
||||
async getAccessById(...args: any[]) {
|
||||
getAccessByIdArgs = args;
|
||||
return {
|
||||
kid: "kid-1",
|
||||
email: "user@example.com",
|
||||
};
|
||||
},
|
||||
async updateAccess(param: any) {
|
||||
updateAccessParam = param;
|
||||
},
|
||||
};
|
||||
autoFix.storageService = {
|
||||
getRepository() {
|
||||
return {
|
||||
async findOne(options: any) {
|
||||
findOneWhere = options.where;
|
||||
return {
|
||||
value: JSON.stringify({
|
||||
value: {
|
||||
privateKey: "legacy-private-key",
|
||||
},
|
||||
}),
|
||||
};
|
||||
},
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
await autoFix.fixGoogleCommonEabAccountKey();
|
||||
|
||||
assert.deepEqual(getAccessByIdArgs, [12, false]);
|
||||
assert.deepEqual(findOneWhere, buildLegacyGoogleAccountConfigWhere("user@example.com"));
|
||||
assert.deepEqual(updateAccessParam, {
|
||||
id: 12,
|
||||
eabType: "google",
|
||||
accountKey: buildEabAccountKeyValue("kid-1", "legacy-private-key"),
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
@@ -0,0 +1,107 @@
|
||||
import { Inject, Provide, Scope, ScopeEnum } from "@midwayjs/core";
|
||||
import { logger } from "@certd/basic";
|
||||
import { AccessService } from "@certd/lib-server";
|
||||
import { isComm } from "@certd/plus-core";
|
||||
import { PluginConfigService } from "../plugin/service/plugin-config-service.js";
|
||||
import { StorageService } from "../pipeline/service/storage-service.js";
|
||||
|
||||
export function parseStorageValue(value?: string) {
|
||||
if (!value) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
const parsed = JSON.parse(value);
|
||||
return parsed?.value || null;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export function buildEabAccountKeyValue(kid: string, privateKey: string) {
|
||||
return JSON.stringify({
|
||||
kid,
|
||||
privateKey,
|
||||
});
|
||||
}
|
||||
|
||||
export function buildLegacyGoogleAccountConfigWhere(email: string) {
|
||||
return {
|
||||
userId: 1,
|
||||
scope: "user",
|
||||
namespace: "1",
|
||||
key: `acme.config.google.${email}`,
|
||||
};
|
||||
}
|
||||
|
||||
@Provide()
|
||||
@Scope(ScopeEnum.Request, { allowDowngrade: true })
|
||||
export class AutoFix {
|
||||
@Inject()
|
||||
pluginConfigService: PluginConfigService;
|
||||
|
||||
@Inject()
|
||||
accessService: AccessService;
|
||||
|
||||
@Inject()
|
||||
storageService: StorageService;
|
||||
|
||||
async init() {
|
||||
await this.fixGoogleCommonEabAccountKey();
|
||||
}
|
||||
async fixGoogleCommonEabAccountKey() {
|
||||
if (!isComm()) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const certApplyConfig = await this.pluginConfigService.getPluginConfig({
|
||||
name: "CertApply",
|
||||
type: "builtIn",
|
||||
});
|
||||
const googleCommonEabAccessId = certApplyConfig?.sysSetting?.input?.googleCommonEabAccessId;
|
||||
if (!googleCommonEabAccessId) {
|
||||
return;
|
||||
}
|
||||
|
||||
const eabAccess = await this.accessService.getAccessById(googleCommonEabAccessId, false);
|
||||
if (eabAccess.accountKey) {
|
||||
return;
|
||||
}
|
||||
if (!eabAccess.kid) {
|
||||
logger.info("公共Google EAB授权缺少KID,跳过历史ACME账号私钥修复");
|
||||
return;
|
||||
}
|
||||
|
||||
const accountConfig = await this.getLegacyGoogleAccountConfig(eabAccess.email);
|
||||
const privateKey = accountConfig?.privateKey || accountConfig?.key || accountConfig?.accountKey;
|
||||
if (!privateKey) {
|
||||
logger.info("未找到可迁移到公共Google EAB授权的历史ACME账号私钥");
|
||||
return;
|
||||
}
|
||||
|
||||
const accountKey = buildEabAccountKeyValue(eabAccess.kid, privateKey);
|
||||
await this.accessService.updateAccess({ id: googleCommonEabAccessId, eabType: "google", accountKey });
|
||||
logger.info(`已修复公共Google EAB授权的ACME账号私钥,accessId=${googleCommonEabAccessId}`);
|
||||
} catch (e: any) {
|
||||
logger.error("修复公共Google EAB授权ACME账号私钥失败", e);
|
||||
}
|
||||
}
|
||||
|
||||
async getLegacyGoogleAccountConfig(email?: string) {
|
||||
if (!email) {
|
||||
return null;
|
||||
}
|
||||
const repository = this.storageService.getRepository();
|
||||
const exact = await repository.findOne({
|
||||
where: buildLegacyGoogleAccountConfigWhere(email),
|
||||
});
|
||||
const exactValue = this.parseStorageValue(exact?.value);
|
||||
if (exactValue?.key || exactValue?.privateKey || exactValue?.accountKey) {
|
||||
return exactValue;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
parseStorageValue(value?: string) {
|
||||
return parseStorageValue(value);
|
||||
}
|
||||
}
|
||||
+3
-4
@@ -1,14 +1,14 @@
|
||||
import { logger } from '@certd/basic';
|
||||
import { PlusService, SysInstallInfo, SysPrivateSettings, SysSettingsService } from '@certd/lib-server';
|
||||
import { Autoload, Config, Init, Inject, Scope, ScopeEnum } from '@midwayjs/core';
|
||||
import { Config, Inject, Provide, Scope, ScopeEnum } from '@midwayjs/core';
|
||||
import crypto from 'crypto';
|
||||
import { nanoid } from 'nanoid';
|
||||
import { UserService } from '../sys/authority/service/user-service.js';
|
||||
import { SafeService } from "../sys/settings/safe-service.js";
|
||||
|
||||
@Autoload()
|
||||
@Provide()
|
||||
@Scope(ScopeEnum.Request, { allowDowngrade: true })
|
||||
export class AutoAInitSite {
|
||||
export class AutoInitSite {
|
||||
@Inject()
|
||||
userService: UserService;
|
||||
|
||||
@@ -22,7 +22,6 @@ export class AutoAInitSite {
|
||||
@Inject()
|
||||
safeService: SafeService;
|
||||
|
||||
@Init()
|
||||
async init() {
|
||||
logger.info('初始化站点开始');
|
||||
await this.startOptimizeDb();
|
||||
+3
-4
@@ -1,16 +1,15 @@
|
||||
import { Autoload, Init, Inject, Scope, ScopeEnum } from "@midwayjs/core";
|
||||
import { Inject, Provide, Scope, ScopeEnum } from "@midwayjs/core";
|
||||
import { logger } from "@certd/basic";
|
||||
import { PluginService } from "../plugin/service/plugin-service.js";
|
||||
import { registerPaymentProviders } from "../suite/payments/index.js";
|
||||
|
||||
@Autoload()
|
||||
@Provide()
|
||||
@Scope(ScopeEnum.Request, { allowDowngrade: true })
|
||||
export class AutoBLoadPlugins {
|
||||
export class AutoLoadPlugins {
|
||||
@Inject()
|
||||
pluginService: PluginService;
|
||||
|
||||
|
||||
@Init()
|
||||
async init() {
|
||||
logger.info(`加载插件开始,加载模式:${process.env.certd_plugin_loadmode}`);
|
||||
if (process.env.certd_plugin_loadmode === "metadata") {
|
||||
+3
-4
@@ -1,14 +1,13 @@
|
||||
import { logger, utils } from '@certd/basic';
|
||||
import { UserSuiteService } from '@certd/commercial-core';
|
||||
import { Autoload, Init, Inject, Scope, ScopeEnum } from '@midwayjs/core';
|
||||
import { Inject, Provide, Scope, ScopeEnum } from '@midwayjs/core';
|
||||
|
||||
@Autoload()
|
||||
@Provide()
|
||||
@Scope(ScopeEnum.Request, { allowDowngrade: true })
|
||||
export class AutoDMitterRegister {
|
||||
export class AutoMitterRegister {
|
||||
@Inject()
|
||||
userSuiteService: UserSuiteService;
|
||||
|
||||
@Init()
|
||||
async init() {
|
||||
await this.registerOnNewUser();
|
||||
}
|
||||
+5
-5
@@ -1,21 +1,21 @@
|
||||
import { Autoload, Init, Inject, Scope, ScopeEnum } from "@midwayjs/core";
|
||||
import { Inject, Provide, Scope, ScopeEnum } from "@midwayjs/core";
|
||||
import { CertInfoService } from "../monitor/index.js";
|
||||
import { pipelineEmitter } from "@certd/pipeline";
|
||||
import { CertInfo, EVENT_CERT_APPLY_SUCCESS } from "@certd/plugin-cert";
|
||||
import { PipelineEvent } from "@certd/pipeline";
|
||||
|
||||
@Autoload()
|
||||
@Provide()
|
||||
@Scope(ScopeEnum.Request, { allowDowngrade: true })
|
||||
export class AutoEPipelineEmitterRegister {
|
||||
export class AutoPipelineEmitterRegister {
|
||||
@Inject()
|
||||
certInfoService: CertInfoService;
|
||||
|
||||
@Init()
|
||||
async init() {
|
||||
await this.onCertApplySuccess();
|
||||
}
|
||||
|
||||
async onCertApplySuccess() {
|
||||
pipelineEmitter.on(EVENT_CERT_APPLY_SUCCESS, async (event: PipelineEvent<{cert:CertInfo,file:string}>) => {
|
||||
pipelineEmitter.on(EVENT_CERT_APPLY_SUCCESS, async (event: PipelineEvent<{ cert: CertInfo; file: string }>) => {
|
||||
await this.certInfoService.updateCertByPipelineId(event.pipeline.id, event.event.cert, event.event.file);
|
||||
});
|
||||
}
|
||||
+3
-4
@@ -1,4 +1,4 @@
|
||||
import { App, Autoload, Config, Init, Inject, Scope, ScopeEnum } from '@midwayjs/core';
|
||||
import { App, Config, Inject, Provide, Scope, ScopeEnum } from '@midwayjs/core';
|
||||
import { getPlusInfo, isPlus } from '@certd/plus-core';
|
||||
import { isDev, logger } from '@certd/basic';
|
||||
|
||||
@@ -11,9 +11,9 @@ import { UserService } from '../sys/authority/service/user-service.js';
|
||||
import { UserSettingsService } from '../mine/service/user-settings-service.js';
|
||||
import { startProxyServer } from './proxy/server.js';
|
||||
|
||||
@Autoload()
|
||||
@Provide()
|
||||
@Scope(ScopeEnum.Request, { allowDowngrade: true })
|
||||
export class AutoZPrint {
|
||||
export class AutoPrint {
|
||||
@Inject()
|
||||
sysSettingsService: SysSettingsService;
|
||||
|
||||
@@ -34,7 +34,6 @@ export class AutoZPrint {
|
||||
@Config('system.resetAdminPasswd')
|
||||
private resetAdminPasswd: boolean;
|
||||
|
||||
@Init()
|
||||
async init() {
|
||||
//监听https
|
||||
this.startHttpsServer();
|
||||
@@ -0,0 +1,44 @@
|
||||
import { Autoload, Init, Inject, Scope, ScopeEnum } from "@midwayjs/core";
|
||||
import { AutoInitSite } from "./auto-init-site.js";
|
||||
import { AutoLoadPlugins } from "./auto-load-plugins.js";
|
||||
import { AutoCron } from "./auto-cron.js";
|
||||
import { AutoMitterRegister } from "./auto-mitter-register.js";
|
||||
import { AutoPipelineEmitterRegister } from "./auto-pipeline-emitter-register.js";
|
||||
import { AutoFix } from "./auto-fix.js";
|
||||
import { AutoPrint } from "./auto-print.js";
|
||||
|
||||
@Autoload()
|
||||
@Scope(ScopeEnum.Request, { allowDowngrade: true })
|
||||
export class AutoRegister {
|
||||
@Inject()
|
||||
autoInitSite: AutoInitSite;
|
||||
|
||||
@Inject()
|
||||
autoLoadPlugins: AutoLoadPlugins;
|
||||
|
||||
@Inject()
|
||||
autoCron: AutoCron;
|
||||
|
||||
@Inject()
|
||||
autoMitterRegister: AutoMitterRegister;
|
||||
|
||||
@Inject()
|
||||
autoPipelineEmitterRegister: AutoPipelineEmitterRegister;
|
||||
|
||||
@Inject()
|
||||
autoPrint: AutoPrint;
|
||||
|
||||
@Inject()
|
||||
autoFix: AutoFix;
|
||||
|
||||
@Init()
|
||||
async init() {
|
||||
await this.autoInitSite.init();
|
||||
await this.autoLoadPlugins.init();
|
||||
await this.autoCron.init();
|
||||
await this.autoMitterRegister.init();
|
||||
await this.autoPipelineEmitterRegister.init();
|
||||
await this.autoFix.init();
|
||||
await this.autoPrint.init();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
import assert from "assert";
|
||||
import { EabAccess } from "./eab-access.js";
|
||||
|
||||
describe("EabAccess", () => {
|
||||
it("generates an account key payload for the current kid", async () => {
|
||||
const access = new EabAccess();
|
||||
access.kid = "kid-1";
|
||||
|
||||
const payload = JSON.parse(await access.onGenerateAccountKey());
|
||||
|
||||
assert.equal(payload.kid, "kid-1");
|
||||
assert.match(payload.privateKey, /BEGIN (RSA )?PRIVATE KEY/);
|
||||
});
|
||||
|
||||
it("requires kid before generating the account key payload", async () => {
|
||||
const access = new EabAccess();
|
||||
|
||||
await assert.rejects(() => access.onGenerateAccountKey(), /请先填写KID/);
|
||||
});
|
||||
});
|
||||
@@ -1,4 +1,5 @@
|
||||
import { IsAccess, AccessInput, BaseAccess } from "@certd/pipeline";
|
||||
import * as acme from "@certd/acme-client";
|
||||
|
||||
@IsAccess({
|
||||
name: "eab",
|
||||
@@ -7,6 +8,23 @@ import { IsAccess, AccessInput, BaseAccess } from "@certd/pipeline";
|
||||
icon: "ic:outline-lock",
|
||||
})
|
||||
export class EabAccess extends BaseAccess {
|
||||
@AccessInput({
|
||||
title: "EAB类型",
|
||||
component: {
|
||||
name: "a-select",
|
||||
options: [
|
||||
{ value: "google", label: "Google(免费)", icon: "flat-color-icons:google" },
|
||||
{ value: "zerossl", label: "ZeroSSL(免费)", icon: "emojione:digit-zero" },
|
||||
{ value: "litessl", label: "litessl(免费)", icon: "roentgen:free" },
|
||||
{ value: "sslcom", label: "SSL.com(仅主域名和www免费)", icon: "la:expeditedssl" },
|
||||
],
|
||||
},
|
||||
helper: "请选择EAB类型",
|
||||
required: true,
|
||||
encrypt: false,
|
||||
})
|
||||
eabType = "";
|
||||
|
||||
@AccessInput({
|
||||
title: "KID",
|
||||
component: {
|
||||
@@ -34,10 +52,35 @@ export class EabAccess extends BaseAccess {
|
||||
placeholder: "绑定一个邮箱",
|
||||
},
|
||||
rules: [{ type: "email", message: "请输入正确的邮箱" }],
|
||||
helper: "Google的EAB申请证书,更换邮箱会导致EAB失效,可以在此处绑定一个邮箱避免此问题",
|
||||
helper: "绑定一个邮箱,避免失效",
|
||||
required: true,
|
||||
})
|
||||
email = "";
|
||||
|
||||
@AccessInput({
|
||||
title: "ACME账号私钥",
|
||||
component: {
|
||||
name: "refresh-input",
|
||||
action: "GenerateAccountKey",
|
||||
buttonText: "刷新账号私钥",
|
||||
successMessage: "账号私钥已刷新,请保存授权配置",
|
||||
},
|
||||
required: true,
|
||||
helper: "如果修改了KID,请点击刷新重新生成账号私钥",
|
||||
encrypt: true,
|
||||
})
|
||||
accountKey = "";
|
||||
|
||||
async onGenerateAccountKey() {
|
||||
if (!this.kid) {
|
||||
throw new Error("请先填写KID");
|
||||
}
|
||||
const key = await acme.crypto.createPrivateKey(2048);
|
||||
return JSON.stringify({
|
||||
kid: this.kid,
|
||||
privateKey: key.toString(),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
new EabAccess();
|
||||
|
||||
@@ -0,0 +1,114 @@
|
||||
import assert from "assert";
|
||||
import { AcmeService } from "./acme.js";
|
||||
|
||||
const logger = {
|
||||
info() {},
|
||||
error() {},
|
||||
warn() {},
|
||||
debug() {},
|
||||
};
|
||||
|
||||
describe("AcmeService account config", () => {
|
||||
it("keeps legacy email-based account config when EAB has no saved account key", async () => {
|
||||
const userContext = {
|
||||
async getObj(key: string) {
|
||||
if (key === "acme.config.google.user@example.com") {
|
||||
return {
|
||||
key: "legacy-email-key",
|
||||
accountUrl: "https://dv.acme-v02.api.pki.goog/acme/acct/legacy",
|
||||
};
|
||||
}
|
||||
return null;
|
||||
},
|
||||
async setObj() {},
|
||||
};
|
||||
const service = new AcmeService({
|
||||
userId: 1,
|
||||
userContext: userContext as any,
|
||||
logger: logger as any,
|
||||
sslProvider: "google",
|
||||
eab: {
|
||||
id: 12,
|
||||
kid: "kid-1",
|
||||
hmacKey: "hmac",
|
||||
} as any,
|
||||
domainParser: {} as any,
|
||||
});
|
||||
|
||||
const conf = await service.getAccountConfig("user@example.com", { enabled: false, mappings: {} });
|
||||
|
||||
assert.equal(conf.key, "legacy-email-key");
|
||||
assert.equal(conf.accountUrl, "https://dv.acme-v02.api.pki.goog/acme/acct/legacy");
|
||||
});
|
||||
|
||||
it("uses the account key saved on the EAB access before legacy email config", async () => {
|
||||
const userContext = {
|
||||
async getObj(key: string) {
|
||||
if (key === "acme.config.google.access.12") {
|
||||
return { accountUrl: "https://dv.acme-v02.api.pki.goog/acme/acct/1" };
|
||||
}
|
||||
if (key === "acme.config.google.user@example.com") {
|
||||
return { key: "legacy-email-key" };
|
||||
}
|
||||
return null;
|
||||
},
|
||||
async setObj() {},
|
||||
};
|
||||
const service = new AcmeService({
|
||||
userId: 1,
|
||||
userContext: userContext as any,
|
||||
logger: logger as any,
|
||||
sslProvider: "google",
|
||||
eab: {
|
||||
id: 12,
|
||||
kid: "kid-1",
|
||||
hmacKey: "hmac",
|
||||
accountKey: JSON.stringify({ kid: "kid-1", privateKey: "eab-account-key" }),
|
||||
} as any,
|
||||
domainParser: {} as any,
|
||||
});
|
||||
|
||||
const conf = await service.getAccountConfig("user@example.com", { enabled: false, mappings: {} });
|
||||
|
||||
assert.equal(conf.key, "eab-account-key");
|
||||
assert.equal(conf.accountUrl, "https://dv.acme-v02.api.pki.goog/acme/acct/1");
|
||||
});
|
||||
|
||||
it("rejects an EAB account key generated for another kid", async () => {
|
||||
const service = new AcmeService({
|
||||
userId: 1,
|
||||
userContext: {} as any,
|
||||
logger: logger as any,
|
||||
sslProvider: "google",
|
||||
eab: {
|
||||
id: 12,
|
||||
kid: "kid-2",
|
||||
hmacKey: "hmac",
|
||||
accountKey: JSON.stringify({ kid: "kid-1", privateKey: "eab-account-key" }),
|
||||
} as any,
|
||||
domainParser: {} as any,
|
||||
});
|
||||
|
||||
assert.throws(() => service.getEabAccountPrivateKey(), /请点击刷新重新生成ACME账号私钥/);
|
||||
});
|
||||
|
||||
it("formats expired EAB errors with a Chinese recovery hint", () => {
|
||||
const service = new AcmeService({
|
||||
userId: 1,
|
||||
userContext: {} as any,
|
||||
logger: logger as any,
|
||||
sslProvider: "google",
|
||||
eab: {
|
||||
id: 12,
|
||||
kid: "kid-1",
|
||||
hmacKey: "hmac",
|
||||
} as any,
|
||||
domainParser: {} as any,
|
||||
});
|
||||
|
||||
const error = service.formatCreateAccountError(new Error("Unknown external account binding (EAB) key. This may be due to the EAB key expiring"));
|
||||
|
||||
assert.match(error.message, /EAB授权已失效或已过期/);
|
||||
assert.match(error.message, /请重新获取EAB授权并刷新ACME账号私钥后重试/);
|
||||
});
|
||||
});
|
||||
@@ -49,11 +49,15 @@ export type CertInfo = {
|
||||
};
|
||||
export type SSLProvider = "letsencrypt" | "google" | "zerossl" | "sslcom" | "letsencrypt_staging";
|
||||
export type PrivateKeyType = "rsa_1024" | "rsa_2048" | "rsa_3072" | "rsa_4096" | "ec_256" | "ec_384" | "ec_521";
|
||||
type AcmeEabOptions = ClientExternalAccountBindingOptions & {
|
||||
id?: number;
|
||||
accountKey?: string;
|
||||
};
|
||||
type AcmeServiceOptions = {
|
||||
userContext: IContext;
|
||||
logger: ILogger;
|
||||
sslProvider: SSLProvider;
|
||||
eab?: ClientExternalAccountBindingOptions;
|
||||
eab?: AcmeEabOptions;
|
||||
skipLocalVerify?: boolean;
|
||||
useMappingProxy?: boolean;
|
||||
reverseProxy?: string;
|
||||
@@ -71,7 +75,7 @@ export class AcmeService {
|
||||
logger: ILogger;
|
||||
sslProvider: SSLProvider;
|
||||
skipLocalVerify = true;
|
||||
eab?: ClientExternalAccountBindingOptions;
|
||||
eab?: AcmeEabOptions;
|
||||
constructor(options: AcmeServiceOptions) {
|
||||
this.options = options;
|
||||
this.userContext = options.userContext;
|
||||
@@ -85,7 +89,14 @@ export class AcmeService {
|
||||
}
|
||||
|
||||
async getAccountConfig(email: string, urlMapping: UrlMapping): Promise<any> {
|
||||
const conf = (await this.userContext.getObj(this.buildAccountKey(email))) || {};
|
||||
let conf = (await this.userContext.getObj(this.buildAccountKey(email))) || {};
|
||||
const eabAccountKey = this.getEabAccountPrivateKey();
|
||||
if (eabAccountKey) {
|
||||
conf = {
|
||||
...((await this.userContext.getObj(this.buildAccessAccountKey())) || {}),
|
||||
key: eabAccountKey,
|
||||
};
|
||||
}
|
||||
if (urlMapping && urlMapping.mappings) {
|
||||
for (const key in urlMapping.mappings) {
|
||||
if (Object.prototype.hasOwnProperty.call(urlMapping.mappings, key)) {
|
||||
@@ -104,16 +115,49 @@ export class AcmeService {
|
||||
return `acme.config.${this.sslProvider}.${email}`;
|
||||
}
|
||||
|
||||
buildAccessAccountKey() {
|
||||
return `acme.config.${this.sslProvider}.access.${this.eab.id}`;
|
||||
}
|
||||
|
||||
getEabAccountPrivateKey() {
|
||||
if (!this.eab?.accountKey) {
|
||||
return null;
|
||||
}
|
||||
let accountKey;
|
||||
try {
|
||||
accountKey = JSON.parse(this.eab.accountKey);
|
||||
} catch {
|
||||
return this.eab.accountKey;
|
||||
}
|
||||
if (accountKey.kid !== this.eab.kid) {
|
||||
throw new Error("EAB的KID已变化,请点击刷新重新生成ACME账号私钥");
|
||||
}
|
||||
return accountKey.privateKey;
|
||||
}
|
||||
|
||||
formatCreateAccountError(e: any) {
|
||||
const message = e?.message || "";
|
||||
if (message.includes("Unknown external account binding (EAB) key")) {
|
||||
return new Error(`EAB授权已失效或已过期,请重新获取EAB授权并刷新ACME账号私钥后重试。原始错误:${message}`);
|
||||
}
|
||||
return e;
|
||||
}
|
||||
|
||||
async saveAccountConfig(email: string, conf: any) {
|
||||
if (this.getEabAccountPrivateKey()) {
|
||||
// userContext 跟用户走。公共 EAB 场景下这里仅作为当前用户缓存;
|
||||
// 其他用户会通过 onlyReturnExisting 用同一个账号私钥取回 accountUrl。
|
||||
await this.userContext.setObj(this.buildAccessAccountKey(), { accountUrl: conf.accountUrl });
|
||||
return;
|
||||
}
|
||||
await this.userContext.setObj(this.buildAccountKey(email), conf);
|
||||
}
|
||||
|
||||
async getAcmeClient(email: string): Promise<acme.Client> {
|
||||
|
||||
const directoryUrl = acme.getDirectoryUrl({ sslProvider: this.sslProvider, pkType: this.options.privateKeyType });
|
||||
let targetUrl = directoryUrl.replace("https://", "");
|
||||
targetUrl = targetUrl.substring(0, targetUrl.indexOf("/"));
|
||||
|
||||
|
||||
const mappings = {
|
||||
"acme-v02.api.letsencrypt.org": "le.px.certd.handfree.work",
|
||||
"dv.acme-v02.api.pki.goog": "gg.px.certd.handfree.work",
|
||||
@@ -171,7 +215,23 @@ export class AcmeService {
|
||||
contact: [`mailto:${email}`],
|
||||
externalAccountBinding: this.eab,
|
||||
};
|
||||
await client.createAccount(accountPayload);
|
||||
if (this.getEabAccountPrivateKey()) {
|
||||
try {
|
||||
// RFC 8555 的 newAccount 支持 onlyReturnExisting。
|
||||
// 使用同一个账号私钥时,CA 会返回已存在账号的 URL,不会再次消费 EAB。
|
||||
await client.createAccount({ onlyReturnExisting: true });
|
||||
conf.accountUrl = client.getAccountUrl();
|
||||
await this.saveAccountConfig(email, conf);
|
||||
return client;
|
||||
} catch (e: any) {
|
||||
this.logger.info(`未找到已存在的ACME账号,准备创建新账号:${e.message}`);
|
||||
}
|
||||
}
|
||||
try {
|
||||
await client.createAccount(accountPayload);
|
||||
} catch (e: any) {
|
||||
throw this.formatCreateAccountError(e);
|
||||
}
|
||||
conf.accountUrl = client.getAccountUrl();
|
||||
await this.saveAccountConfig(email, conf);
|
||||
}
|
||||
|
||||
Generated
+112
-57
@@ -94,6 +94,9 @@ importers:
|
||||
chai-as-promised:
|
||||
specifier: ^7.1.2
|
||||
version: 7.1.2(chai@4.5.0)
|
||||
cross-env:
|
||||
specifier: ^7.0.3
|
||||
version: 7.0.3
|
||||
eslint:
|
||||
specifier: ^8.57.0
|
||||
version: 8.57.0
|
||||
@@ -106,6 +109,9 @@ importers:
|
||||
eslint-plugin-prettier:
|
||||
specifier: ^4.2.1
|
||||
version: 4.2.1(eslint-config-prettier@8.10.0(eslint@8.57.0))(eslint@8.57.0)(prettier@2.8.8)
|
||||
esmock:
|
||||
specifier: ^2.7.5
|
||||
version: 2.7.5
|
||||
jsdoc-to-markdown:
|
||||
specifier: ^8.0.1
|
||||
version: 8.0.3
|
||||
@@ -188,6 +194,9 @@ importers:
|
||||
chai:
|
||||
specifier: 4.3.10
|
||||
version: 4.3.10
|
||||
cross-env:
|
||||
specifier: ^7.0.3
|
||||
version: 7.0.3
|
||||
eslint:
|
||||
specifier: ^8.41.0
|
||||
version: 8.57.0
|
||||
@@ -197,6 +206,9 @@ importers:
|
||||
eslint-plugin-prettier:
|
||||
specifier: ^4.2.1
|
||||
version: 4.2.1(eslint-config-prettier@8.10.0(eslint@8.57.0))(eslint@8.57.0)(prettier@2.8.8)
|
||||
esmock:
|
||||
specifier: ^2.7.5
|
||||
version: 2.7.5
|
||||
mocha:
|
||||
specifier: ^10.2.0
|
||||
version: 10.8.2
|
||||
@@ -267,6 +279,9 @@ importers:
|
||||
chai:
|
||||
specifier: 4.3.10
|
||||
version: 4.3.10
|
||||
cross-env:
|
||||
specifier: ^7.0.3
|
||||
version: 7.0.3
|
||||
eslint:
|
||||
specifier: ^8.41.0
|
||||
version: 8.57.0
|
||||
@@ -276,6 +291,9 @@ importers:
|
||||
eslint-plugin-prettier:
|
||||
specifier: ^4.2.1
|
||||
version: 4.2.1(eslint-config-prettier@8.10.0(eslint@8.57.0))(eslint@8.57.0)(prettier@2.8.8)
|
||||
esmock:
|
||||
specifier: ^2.7.5
|
||||
version: 2.7.5
|
||||
mocha:
|
||||
specifier: ^10.2.0
|
||||
version: 10.8.2
|
||||
@@ -313,6 +331,12 @@ importers:
|
||||
'@typescript-eslint/parser':
|
||||
specifier: ^8.26.1
|
||||
version: 8.32.1(eslint@8.57.0)(typescript@5.9.3)
|
||||
cross-env:
|
||||
specifier: ^7.0.3
|
||||
version: 7.0.3
|
||||
esmock:
|
||||
specifier: ^2.7.5
|
||||
version: 2.7.5
|
||||
prettier:
|
||||
specifier: ^2.8.8
|
||||
version: 2.8.8
|
||||
@@ -335,6 +359,9 @@ importers:
|
||||
'@typescript-eslint/parser':
|
||||
specifier: ^8.26.1
|
||||
version: 8.32.1(eslint@8.57.0)(typescript@5.9.3)
|
||||
cross-env:
|
||||
specifier: ^7.0.3
|
||||
version: 7.0.3
|
||||
eslint:
|
||||
specifier: ^8.24.0
|
||||
version: 8.57.0
|
||||
@@ -344,6 +371,9 @@ importers:
|
||||
eslint-plugin-prettier:
|
||||
specifier: ^4.2.1
|
||||
version: 4.2.1(eslint-config-prettier@8.10.0(eslint@8.57.0))(eslint@8.57.0)(prettier@2.8.8)
|
||||
esmock:
|
||||
specifier: ^2.7.5
|
||||
version: 2.7.5
|
||||
prettier:
|
||||
specifier: ^2.8.8
|
||||
version: 2.8.8
|
||||
@@ -403,8 +433,11 @@ importers:
|
||||
specifier: ^1.30.0
|
||||
version: 1.31.0
|
||||
cross-env:
|
||||
specifier: ^5.1.4
|
||||
version: 5.2.1
|
||||
specifier: ^7.0.3
|
||||
version: 7.0.3
|
||||
esmock:
|
||||
specifier: ^2.7.5
|
||||
version: 2.7.5
|
||||
js-yaml:
|
||||
specifier: ^3.11.0
|
||||
version: 3.14.1
|
||||
@@ -436,6 +469,9 @@ importers:
|
||||
'@typescript-eslint/parser':
|
||||
specifier: ^8.26.1
|
||||
version: 8.32.1(eslint@8.57.0)(typescript@5.9.3)
|
||||
cross-env:
|
||||
specifier: ^7.0.3
|
||||
version: 7.0.3
|
||||
eslint:
|
||||
specifier: ^8.24.0
|
||||
version: 8.57.0
|
||||
@@ -445,6 +481,9 @@ importers:
|
||||
eslint-plugin-prettier:
|
||||
specifier: ^4.2.1
|
||||
version: 4.2.1(eslint-config-prettier@8.10.0(eslint@8.57.0))(eslint@8.57.0)(prettier@2.8.8)
|
||||
esmock:
|
||||
specifier: ^2.7.5
|
||||
version: 2.7.5
|
||||
prettier:
|
||||
specifier: ^2.8.8
|
||||
version: 2.8.8
|
||||
@@ -505,9 +544,6 @@ importers:
|
||||
better-sqlite3:
|
||||
specifier: ^11.1.2
|
||||
version: 11.10.0
|
||||
cross-env:
|
||||
specifier: ^7.0.3
|
||||
version: 7.0.3
|
||||
dayjs:
|
||||
specifier: ^1.11.7
|
||||
version: 1.11.13
|
||||
@@ -539,6 +575,9 @@ importers:
|
||||
'@typescript-eslint/parser':
|
||||
specifier: ^8.26.1
|
||||
version: 8.32.1(eslint@8.57.0)(typescript@5.9.3)
|
||||
cross-env:
|
||||
specifier: ^7.0.3
|
||||
version: 7.0.3
|
||||
eslint:
|
||||
specifier: ^8.24.0
|
||||
version: 8.57.0
|
||||
@@ -548,6 +587,9 @@ importers:
|
||||
eslint-plugin-prettier:
|
||||
specifier: ^4.2.1
|
||||
version: 4.2.1(eslint-config-prettier@8.10.0(eslint@8.57.0))(eslint@8.57.0)(prettier@2.8.8)
|
||||
esmock:
|
||||
specifier: ^2.7.5
|
||||
version: 2.7.5
|
||||
mocha:
|
||||
specifier: ^10.2.0
|
||||
version: 10.8.2
|
||||
@@ -594,6 +636,9 @@ importers:
|
||||
'@typescript-eslint/parser':
|
||||
specifier: ^8.26.1
|
||||
version: 8.32.1(eslint@8.57.0)(typescript@5.9.3)
|
||||
cross-env:
|
||||
specifier: ^7.0.3
|
||||
version: 7.0.3
|
||||
eslint:
|
||||
specifier: ^8.24.0
|
||||
version: 8.57.0
|
||||
@@ -609,6 +654,9 @@ importers:
|
||||
eslint-plugin-prettier:
|
||||
specifier: ^4.2.1
|
||||
version: 4.2.1(eslint-config-prettier@8.10.0(eslint@8.57.0))(eslint@8.57.0)(prettier@2.8.8)
|
||||
esmock:
|
||||
specifier: ^2.7.5
|
||||
version: 2.7.5
|
||||
prettier:
|
||||
specifier: ^2.8.8
|
||||
version: 2.8.8
|
||||
@@ -661,6 +709,9 @@ importers:
|
||||
chai:
|
||||
specifier: ^4.3.6
|
||||
version: 4.5.0
|
||||
cross-env:
|
||||
specifier: ^7.0.3
|
||||
version: 7.0.3
|
||||
eslint:
|
||||
specifier: ^8.24.0
|
||||
version: 8.57.0
|
||||
@@ -670,6 +721,9 @@ importers:
|
||||
eslint-plugin-prettier:
|
||||
specifier: ^4.2.1
|
||||
version: 4.2.1(eslint-config-prettier@8.10.0(eslint@8.57.0))(eslint@8.57.0)(prettier@2.8.8)
|
||||
esmock:
|
||||
specifier: ^2.7.5
|
||||
version: 2.7.5
|
||||
mocha:
|
||||
specifier: ^10.1.0
|
||||
version: 10.8.2
|
||||
@@ -776,6 +830,9 @@ importers:
|
||||
chai:
|
||||
specifier: ^4.3.6
|
||||
version: 4.5.0
|
||||
cross-env:
|
||||
specifier: ^7.0.3
|
||||
version: 7.0.3
|
||||
eslint:
|
||||
specifier: ^8.24.0
|
||||
version: 8.57.0
|
||||
@@ -785,6 +842,9 @@ importers:
|
||||
eslint-plugin-prettier:
|
||||
specifier: ^4.2.1
|
||||
version: 4.2.1(eslint-config-prettier@8.10.0(eslint@8.57.0))(eslint@8.57.0)(prettier@2.8.8)
|
||||
esmock:
|
||||
specifier: ^2.7.5
|
||||
version: 2.7.5
|
||||
mocha:
|
||||
specifier: ^10.1.0
|
||||
version: 10.8.2
|
||||
@@ -861,6 +921,9 @@ importers:
|
||||
'@typescript-eslint/parser':
|
||||
specifier: ^8.26.1
|
||||
version: 8.32.1(eslint@8.57.0)(typescript@5.9.3)
|
||||
cross-env:
|
||||
specifier: ^7.0.3
|
||||
version: 7.0.3
|
||||
eslint:
|
||||
specifier: ^8.24.0
|
||||
version: 8.57.0
|
||||
@@ -870,6 +933,9 @@ importers:
|
||||
eslint-plugin-prettier:
|
||||
specifier: ^4.2.1
|
||||
version: 4.2.1(eslint-config-prettier@8.10.0(eslint@8.57.0))(eslint@8.57.0)(prettier@2.8.8)
|
||||
esmock:
|
||||
specifier: ^2.7.5
|
||||
version: 2.7.5
|
||||
mocha:
|
||||
specifier: ^10.2.0
|
||||
version: 10.8.2
|
||||
@@ -952,6 +1018,9 @@ importers:
|
||||
chai:
|
||||
specifier: 4.3.10
|
||||
version: 4.3.10
|
||||
cross-env:
|
||||
specifier: ^7.0.3
|
||||
version: 7.0.3
|
||||
eslint:
|
||||
specifier: ^8.41.0
|
||||
version: 8.57.0
|
||||
@@ -961,6 +1030,9 @@ importers:
|
||||
eslint-plugin-prettier:
|
||||
specifier: ^4.2.1
|
||||
version: 4.2.1(eslint-config-prettier@8.10.0(eslint@8.57.0))(eslint@8.57.0)(prettier@2.8.8)
|
||||
esmock:
|
||||
specifier: ^2.7.5
|
||||
version: 2.7.5
|
||||
mocha:
|
||||
specifier: ^10.2.0
|
||||
version: 10.8.2
|
||||
@@ -1019,6 +1091,9 @@ importers:
|
||||
chai:
|
||||
specifier: 4.3.10
|
||||
version: 4.3.10
|
||||
cross-env:
|
||||
specifier: ^7.0.3
|
||||
version: 7.0.3
|
||||
eslint:
|
||||
specifier: ^8.41.0
|
||||
version: 8.57.0
|
||||
@@ -1028,6 +1103,9 @@ importers:
|
||||
eslint-plugin-prettier:
|
||||
specifier: ^4.2.1
|
||||
version: 4.2.1(eslint-config-prettier@8.10.0(eslint@8.57.0))(eslint@8.57.0)(prettier@2.8.8)
|
||||
esmock:
|
||||
specifier: ^2.7.5
|
||||
version: 2.7.5
|
||||
mocha:
|
||||
specifier: ^10.2.0
|
||||
version: 10.8.2
|
||||
@@ -1154,9 +1232,6 @@ importers:
|
||||
cropperjs:
|
||||
specifier: ^1.6.1
|
||||
version: 1.6.2
|
||||
cross-env:
|
||||
specifier: ^7.0.3
|
||||
version: 7.0.3
|
||||
cssnano:
|
||||
specifier: ^7.0.6
|
||||
version: 7.0.7(postcss@8.5.6)
|
||||
@@ -1344,6 +1419,9 @@ importers:
|
||||
chai:
|
||||
specifier: ^5.1.0
|
||||
version: 5.2.0
|
||||
cross-env:
|
||||
specifier: ^7.0.3
|
||||
version: 7.0.3
|
||||
dependency-cruiser:
|
||||
specifier: ^16.2.3
|
||||
version: 16.10.2
|
||||
@@ -1371,6 +1449,9 @@ importers:
|
||||
eslint-plugin-vue:
|
||||
specifier: ^9.23.0
|
||||
version: 9.33.0(eslint@8.57.0)
|
||||
esmock:
|
||||
specifier: ^2.7.5
|
||||
version: 2.7.5
|
||||
less:
|
||||
specifier: ^4.2.0
|
||||
version: 4.3.0
|
||||
@@ -1632,9 +1713,6 @@ importers:
|
||||
cron-parser:
|
||||
specifier: ^4.9.0
|
||||
version: 4.9.0
|
||||
cross-env:
|
||||
specifier: ^7.0.3
|
||||
version: 7.0.3
|
||||
crypto-js:
|
||||
specifier: ^4.2.0
|
||||
version: 4.2.0
|
||||
@@ -1801,6 +1879,12 @@ importers:
|
||||
c8:
|
||||
specifier: ^10.1.2
|
||||
version: 10.1.3
|
||||
cross-env:
|
||||
specifier: ^7.0.3
|
||||
version: 7.0.3
|
||||
esmock:
|
||||
specifier: ^2.7.5
|
||||
version: 2.7.5
|
||||
mocha:
|
||||
specifier: ^10.2.0
|
||||
version: 10.8.2
|
||||
@@ -4384,56 +4468,67 @@ packages:
|
||||
resolution: {integrity: sha512-u72Mzc6jyJwKjJbZZcIYmd9bumJu7KNmHYdue43vT1rXPm2rITwmPWF0mmPzLm9/vJWxIRbao/jrQmxTO0Sm9w==}
|
||||
cpu: [arm]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@rollup/rollup-linux-arm-musleabihf@4.50.0':
|
||||
resolution: {integrity: sha512-S4UefYdV0tnynDJV1mdkNawp0E5Qm2MtSs330IyHgaccOFrwqsvgigUD29uT+B/70PDY1eQ3t40+xf6wIvXJyg==}
|
||||
cpu: [arm]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@rollup/rollup-linux-arm64-gnu@4.50.0':
|
||||
resolution: {integrity: sha512-1EhkSvUQXJsIhk4msxP5nNAUWoB4MFDHhtc4gAYvnqoHlaL9V3F37pNHabndawsfy/Tp7BPiy/aSa6XBYbaD1g==}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@rollup/rollup-linux-arm64-musl@4.50.0':
|
||||
resolution: {integrity: sha512-EtBDIZuDtVg75xIPIK1l5vCXNNCIRM0OBPUG+tbApDuJAy9mKago6QxX+tfMzbCI6tXEhMuZuN1+CU8iDW+0UQ==}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@rollup/rollup-linux-loongarch64-gnu@4.50.0':
|
||||
resolution: {integrity: sha512-BGYSwJdMP0hT5CCmljuSNx7+k+0upweM2M4YGfFBjnFSZMHOLYR0gEEj/dxyYJ6Zc6AiSeaBY8dWOa11GF/ppQ==}
|
||||
cpu: [loong64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@rollup/rollup-linux-ppc64-gnu@4.50.0':
|
||||
resolution: {integrity: sha512-I1gSMzkVe1KzAxKAroCJL30hA4DqSi+wGc5gviD0y3IL/VkvcnAqwBf4RHXHyvH66YVHxpKO8ojrgc4SrWAnLg==}
|
||||
cpu: [ppc64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@rollup/rollup-linux-riscv64-gnu@4.50.0':
|
||||
resolution: {integrity: sha512-bSbWlY3jZo7molh4tc5dKfeSxkqnf48UsLqYbUhnkdnfgZjgufLS/NTA8PcP/dnvct5CCdNkABJ56CbclMRYCA==}
|
||||
cpu: [riscv64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@rollup/rollup-linux-riscv64-musl@4.50.0':
|
||||
resolution: {integrity: sha512-LSXSGumSURzEQLT2e4sFqFOv3LWZsEF8FK7AAv9zHZNDdMnUPYH3t8ZlaeYYZyTXnsob3htwTKeWtBIkPV27iQ==}
|
||||
cpu: [riscv64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@rollup/rollup-linux-s390x-gnu@4.50.0':
|
||||
resolution: {integrity: sha512-CxRKyakfDrsLXiCyucVfVWVoaPA4oFSpPpDwlMcDFQvrv3XY6KEzMtMZrA+e/goC8xxp2WSOxHQubP8fPmmjOQ==}
|
||||
cpu: [s390x]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@rollup/rollup-linux-x64-gnu@4.50.0':
|
||||
resolution: {integrity: sha512-8PrJJA7/VU8ToHVEPu14FzuSAqVKyo5gg/J8xUerMbyNkWkO9j2ExBho/68RnJsMGNJq4zH114iAttgm7BZVkA==}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@rollup/rollup-linux-x64-musl@4.50.0':
|
||||
resolution: {integrity: sha512-SkE6YQp+CzpyOrbw7Oc4MgXFvTw2UIBElvAvLCo230pyxOLmYwRPwZ/L5lBe/VW/qT1ZgND9wJfOsdy0XptRvw==}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@rollup/rollup-openharmony-arm64@4.50.0':
|
||||
resolution: {integrity: sha512-PZkNLPfvXeIOgJWA804zjSFH7fARBBCpCXxgkGDRjjAhRLOR8o0IGS01ykh5GYfod4c2yiiREuDM8iZ+pVsT+Q==}
|
||||
@@ -6807,20 +6902,11 @@ packages:
|
||||
cropperjs@1.6.2:
|
||||
resolution: {integrity: sha512-nhymn9GdnV3CqiEHJVai54TULFAE3VshJTXSqSJKa8yXAKyBKDWdhHarnlIPrshJ0WMFTGuFvG02YjLXfPiuOA==}
|
||||
|
||||
cross-env@5.2.1:
|
||||
resolution: {integrity: sha512-1yHhtcfAd1r4nwQgknowuUNfIT9E8dOMMspC36g45dN+iD1blloi7xp8X/xAIDnjHWyt1uQ8PHk2fkNaym7soQ==}
|
||||
engines: {node: '>=4.0'}
|
||||
hasBin: true
|
||||
|
||||
cross-env@7.0.3:
|
||||
resolution: {integrity: sha512-+/HKd6EgcQCJGh2PSjZuUitQBQynKor4wrFbRg4DtAgS1aWO+gU52xpH7M9ScGgXSYmAVS9bIJ8EzuaGw0oNAw==}
|
||||
engines: {node: '>=10.14', npm: '>=6', yarn: '>=1'}
|
||||
hasBin: true
|
||||
|
||||
cross-spawn@6.0.6:
|
||||
resolution: {integrity: sha512-VqCUuhcd1iB+dsv8gxPttb5iZh/D0iubSP21g36KXdEuf6I5JiioesUVjpCdHV9MZRUfVFlvwtIUyPfxo5trtw==}
|
||||
engines: {node: '>=4.8'}
|
||||
|
||||
cross-spawn@7.0.6:
|
||||
resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==}
|
||||
engines: {node: '>= 8'}
|
||||
@@ -7600,6 +7686,10 @@ packages:
|
||||
deprecated: This version is no longer supported. Please see https://eslint.org/version-support for other options.
|
||||
hasBin: true
|
||||
|
||||
esmock@2.7.5:
|
||||
resolution: {integrity: sha512-jKwL7yYpVOERalCllSnPur59s9M0gV5BKijtmOKclqDMuhqdS7DT/a7cODjz6w1XusE0wAaHBTrK+zgab/ENgw==}
|
||||
engines: {node: '>=14.16.0'}
|
||||
|
||||
esniff@2.0.1:
|
||||
resolution: {integrity: sha512-kTUIGKQ/mDPFoJ0oVfcmyJn4iBDRptjNVIzwIFR7tqWXdVI9xfA2RMwY/gbSpJG3lkdWNEjLap/NqVHZiJsdfg==}
|
||||
engines: {node: '>=0.10'}
|
||||
@@ -9822,9 +9912,6 @@ packages:
|
||||
next-tick@1.1.0:
|
||||
resolution: {integrity: sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ==}
|
||||
|
||||
nice-try@1.0.5:
|
||||
resolution: {integrity: sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==}
|
||||
|
||||
no-case@3.0.4:
|
||||
resolution: {integrity: sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==}
|
||||
|
||||
@@ -10234,10 +10321,6 @@ packages:
|
||||
resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
path-key@2.0.1:
|
||||
resolution: {integrity: sha512-fEHGKCSmUSDPv4uoj8AlD+joPlq3peND+HRYyxFz4KPw4z926S/b8rIuFs2FYJg3BwsxJf6A9/3eIdLaYC+9Dw==}
|
||||
engines: {node: '>=4'}
|
||||
|
||||
path-key@3.1.1:
|
||||
resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==}
|
||||
engines: {node: '>=8'}
|
||||
@@ -11475,18 +11558,10 @@ packages:
|
||||
shallow-equal@1.2.1:
|
||||
resolution: {integrity: sha512-S4vJDjHHMBaiZuT9NPb616CSmLf618jawtv3sufLl6ivK8WocjAo58cXwbRV1cgqxH0Qbv+iUt6m05eqEa2IRA==}
|
||||
|
||||
shebang-command@1.2.0:
|
||||
resolution: {integrity: sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
shebang-command@2.0.0:
|
||||
resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
shebang-regex@1.0.0:
|
||||
resolution: {integrity: sha512-wpoSFAxys6b2a2wHZ1XpDSgD7N9iVjg29Ph9uV/uaP9Ex/KXlkTZTeddxDPSYQpgvzKLGJke2UU0AzoGCjNIvQ==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
shebang-regex@3.0.0:
|
||||
resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==}
|
||||
engines: {node: '>=8'}
|
||||
@@ -20418,22 +20493,10 @@ snapshots:
|
||||
|
||||
cropperjs@1.6.2: {}
|
||||
|
||||
cross-env@5.2.1:
|
||||
dependencies:
|
||||
cross-spawn: 6.0.6
|
||||
|
||||
cross-env@7.0.3:
|
||||
dependencies:
|
||||
cross-spawn: 7.0.6
|
||||
|
||||
cross-spawn@6.0.6:
|
||||
dependencies:
|
||||
nice-try: 1.0.5
|
||||
path-key: 2.0.1
|
||||
semver: 5.7.2
|
||||
shebang-command: 1.2.0
|
||||
which: 1.3.1
|
||||
|
||||
cross-spawn@7.0.6:
|
||||
dependencies:
|
||||
path-key: 3.1.1
|
||||
@@ -21439,6 +21502,8 @@ snapshots:
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
esmock@2.7.5: {}
|
||||
|
||||
esniff@2.0.1:
|
||||
dependencies:
|
||||
d: 1.0.2
|
||||
@@ -23862,8 +23927,6 @@ snapshots:
|
||||
|
||||
next-tick@1.1.0: {}
|
||||
|
||||
nice-try@1.0.5: {}
|
||||
|
||||
no-case@3.0.4:
|
||||
dependencies:
|
||||
lower-case: 2.0.2
|
||||
@@ -24331,8 +24394,6 @@ snapshots:
|
||||
|
||||
path-is-absolute@1.0.1: {}
|
||||
|
||||
path-key@2.0.1: {}
|
||||
|
||||
path-key@3.1.1: {}
|
||||
|
||||
path-key@4.0.0: {}
|
||||
@@ -25682,16 +25743,10 @@ snapshots:
|
||||
|
||||
shallow-equal@1.2.1: {}
|
||||
|
||||
shebang-command@1.2.0:
|
||||
dependencies:
|
||||
shebang-regex: 1.0.0
|
||||
|
||||
shebang-command@2.0.0:
|
||||
dependencies:
|
||||
shebang-regex: 3.0.0
|
||||
|
||||
shebang-regex@1.0.0: {}
|
||||
|
||||
shebang-regex@3.0.0: {}
|
||||
|
||||
shiki@3.4.1:
|
||||
|
||||
Reference in New Issue
Block a user