diff --git a/AGENTS.md b/AGENTS.md index 3fb9c8ca2..e878d1064 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -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 等通常视为运行时或构建产物,除非任务明确要求处理它们。 - 注意本地数据和配置里可能包含凭据、证书材料等敏感信息。 +- 本仓库代码注释优先使用中文,尤其是解释业务规则、兼容逻辑、协议细节和隐藏风险时;除非文件已有明确英文注释风格或引用外部英文术语,否则不要新增英文说明性注释。 ## 插件开发技能 @@ -207,6 +214,7 @@ Get-ChildItem packages\ui\certd-client\src\views\certd - 实现新功能或修复行为缺陷前,先补对应单元测试,并先运行测试确认它处于失败状态;再实现功能或修复代码,反复运行聚焦单元测试直到通过。若某项改动确实不适合先写单元测试,应在回复中说明原因和替代验证方式。 - 后补单元测试时,应先基于对正确行为的实际预期编写测试,而不是为了迎合现有实现改写预期;如果运行后出现红灯,且通过测试需要修改已有实现,应先向用户确认这是确实的 bug,还是原本需求/既有行为就是如此;确认后再修改原始实现,避免把测试补充变成未经确认的行为改动。 - 后端纯单元测试用例放在 `src` 目录内,并尽量与被测文件相邻,例如 `src/utils/random.test.ts`;对应 `test:unit` 只跑 `src/**/*.test.ts`,构建/打包配置应排除这些 `*.test.ts` 文件。 +- 单元测试需要 mock ESM 静态 import 时,优先使用 `esmock`,不要为了测试把业务代码改成构造函数注入或把逻辑挪到调用方;各包 `test:unit` 脚本应显式设置 `NODE_ENV=unittest`。 - 单个 monorepo 包运行单元测试时,优先使用 `corepack pnpm --dir <包目录> test:unit`,例如 `corepack pnpm --dir packages\ui\certd-server test:unit`、`corepack pnpm --dir packages\core\basic test:unit`、`corepack pnpm --dir packages\plugins\plugin-lib test:unit`;也可以用包名过滤,例如 `corepack pnpm --filter @certd/ui-server test:unit`。前端 `packages\ui\certd-client` 暂时不跑单元测试。 - 前端 TS/Vue/locale 等文件改动后,优先只对本次改动文件运行项目现有自动格式化/修复;Windows/PowerShell 下 Prettier 已验证可用命令为 `packages\ui\certd-client\node_modules\.bin\prettier.cmd --write `,ESLint 可用命令为 `packages\ui\certd-client\node_modules\.bin\eslint.cmd --fix `;不要为了格式化无关文件而扩大 diff。项目保留了 `tslint` 依赖,但当前主要使用 ESLint + Prettier。 - 优先对改动包运行聚焦的测试或类型检查;只有跨包影响明显时再考虑全 monorepo 构建。 diff --git a/CHANGELOG.md b/CHANGELOG.md index d8cb7fcfe..4631c6643 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,26 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [1.39.13](https://github.com/certd/certd/compare/v1.39.12...v1.39.13) (2026-05-10) + +### Bug Fixes + +* **aliyun-access:** 添加阿里云密钥校验失败的错误处理 ([b75c625](https://github.com/certd/certd/commit/b75c625ddcc0b3110699d8e6175681ef157b25df)) +* cnameProvider域名支持设置子域名托管 ([7266af1](https://github.com/certd/certd/commit/7266af17491a98338022cfb18cfedfb93ca6ef8f)) +* **plugin-aliyun:** 过滤非CAS证书并优化日志信息 ([c4b01da](https://github.com/certd/certd/commit/c4b01da384bc40a241a673ea8bc01ca733c04d83)) + +### Performance Improvements + +* **设置:** 添加首页启用开关配置 ([25ad1e6](https://github.com/certd/certd/commit/25ad1e6f861e43288cc8bd90d4903628e6faec29)) +* 新增agents.md ([aa176b0](https://github.com/certd/certd/commit/aa176b081a92837d2d6809d16546a8dfc2e5dd36)) +* **用户资料:** 新增手机号邮箱绑定功能 ([e0eb0e2](https://github.com/certd/certd/commit/e0eb0e21f6dae24b639c944f9aba2c90496ab1c0)) +* 域名注册过期时间获取再次优化 ([91a1b97](https://github.com/certd/certd/commit/91a1b9755066bf280e194dabf7c3a9f936e2643f)) +* **证书流水线:** 添加批量更新证书申请参数功能 ([63be1c1](https://github.com/certd/certd/commit/63be1c1cbd9b09a3b48f26130c296b1cedcca1ac)) +* 支持火山云vke ([bb46cb0](https://github.com/certd/certd/commit/bb46cb08f71f6ae921543f7e4a6c5f4e0190556e)) +* 重构自动加载模块并优化EAB授权处理 ([4755216](https://github.com/certd/certd/commit/4755216505ad18555a50da9d8008c2207c48df86)) +* **domain:** 添加域名过期时间同步进度显示功能 ([9d2937d](https://github.com/certd/certd/commit/9d2937dd4b14ffab73e9b096edd2aa8539811182)) +* **plugin-volcengine:** 支持火山引擎VKE部署插件 ([b8a64a6](https://github.com/certd/certd/commit/b8a64a6b5bf3691a47177de42bc49b798e795feb)) + ## [1.39.12](https://github.com/certd/certd/compare/v1.39.11...v1.39.12) (2026-04-29) ### Bug Fixes diff --git a/docs/guide/changelogs/CHANGELOG.md b/docs/guide/changelogs/CHANGELOG.md index d8cb7fcfe..4631c6643 100644 --- a/docs/guide/changelogs/CHANGELOG.md +++ b/docs/guide/changelogs/CHANGELOG.md @@ -3,6 +3,26 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [1.39.13](https://github.com/certd/certd/compare/v1.39.12...v1.39.13) (2026-05-10) + +### Bug Fixes + +* **aliyun-access:** 添加阿里云密钥校验失败的错误处理 ([b75c625](https://github.com/certd/certd/commit/b75c625ddcc0b3110699d8e6175681ef157b25df)) +* cnameProvider域名支持设置子域名托管 ([7266af1](https://github.com/certd/certd/commit/7266af17491a98338022cfb18cfedfb93ca6ef8f)) +* **plugin-aliyun:** 过滤非CAS证书并优化日志信息 ([c4b01da](https://github.com/certd/certd/commit/c4b01da384bc40a241a673ea8bc01ca733c04d83)) + +### Performance Improvements + +* **设置:** 添加首页启用开关配置 ([25ad1e6](https://github.com/certd/certd/commit/25ad1e6f861e43288cc8bd90d4903628e6faec29)) +* 新增agents.md ([aa176b0](https://github.com/certd/certd/commit/aa176b081a92837d2d6809d16546a8dfc2e5dd36)) +* **用户资料:** 新增手机号邮箱绑定功能 ([e0eb0e2](https://github.com/certd/certd/commit/e0eb0e21f6dae24b639c944f9aba2c90496ab1c0)) +* 域名注册过期时间获取再次优化 ([91a1b97](https://github.com/certd/certd/commit/91a1b9755066bf280e194dabf7c3a9f936e2643f)) +* **证书流水线:** 添加批量更新证书申请参数功能 ([63be1c1](https://github.com/certd/certd/commit/63be1c1cbd9b09a3b48f26130c296b1cedcca1ac)) +* 支持火山云vke ([bb46cb0](https://github.com/certd/certd/commit/bb46cb08f71f6ae921543f7e4a6c5f4e0190556e)) +* 重构自动加载模块并优化EAB授权处理 ([4755216](https://github.com/certd/certd/commit/4755216505ad18555a50da9d8008c2207c48df86)) +* **domain:** 添加域名过期时间同步进度显示功能 ([9d2937d](https://github.com/certd/certd/commit/9d2937dd4b14ffab73e9b096edd2aa8539811182)) +* **plugin-volcengine:** 支持火山引擎VKE部署插件 ([b8a64a6](https://github.com/certd/certd/commit/b8a64a6b5bf3691a47177de42bc49b798e795feb)) + ## [1.39.12](https://github.com/certd/certd/compare/v1.39.11...v1.39.12) (2026-04-29) ### Bug Fixes diff --git a/docs/guide/plugins/deploy.md b/docs/guide/plugins/deploy.md index 610d636d3..18708ebf4 100644 --- a/docs/guide/plugins/deploy.md +++ b/docs/guide/plugins/deploy.md @@ -1,5 +1,5 @@ # 任务插件 -共 `132` 款任务插件 +共 `131` 款任务插件 ## 1. 证书申请 | 序号 | 名称 | 说明 | diff --git a/lerna.json b/lerna.json index 2bedae35a..677b8ef95 100644 --- a/lerna.json +++ b/lerna.json @@ -9,5 +9,5 @@ } }, "npmClient": "pnpm", - "version": "1.39.12" + "version": "1.39.13" } diff --git a/package.json b/package.json index fe3a29a2f..ab2d70812 100644 --- a/package.json +++ b/package.json @@ -9,6 +9,7 @@ "@lerna-lite/run": "^3.9.3", "@lerna-lite/version": "^3.9.3", "axios": "^1.9.0", + "cross-env": "^7.0.3", "medium-zoom": "^1.1.0", "vitepress": "^2.0.0-alpha.4", "vitepress-plugin-lightbox": "^1.0.2" @@ -35,7 +36,7 @@ "docs:dev": "vitepress dev docs", "docs:build": "pnpm run copylogs && vitepress build docs", "docs:preview": "vitepress preview docs", - "test:unit": "pnpm -r --workspace-concurrency=1 run test:unit", + "test:unit": "cross-env NODE_ENV=unittest pnpm -r --workspace-concurrency=1 run test:unit", "pub": "echo 1", "dev": "pnpm run -r --parallel compile ", "pub_all":"pnpm run -r --parallel pub ", diff --git a/packages/core/acme-client/CHANGELOG.md b/packages/core/acme-client/CHANGELOG.md index 62b0c02eb..cbd12f3c3 100644 --- a/packages/core/acme-client/CHANGELOG.md +++ b/packages/core/acme-client/CHANGELOG.md @@ -3,6 +3,12 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [1.39.13](https://github.com/publishlab/node-acme-client/compare/v1.39.12...v1.39.13) (2026-05-10) + +### Performance Improvements + +* 重构自动加载模块并优化EAB授权处理 ([4755216](https://github.com/publishlab/node-acme-client/commit/4755216505ad18555a50da9d8008c2207c48df86)) + ## [1.39.12](https://github.com/publishlab/node-acme-client/compare/v1.39.11...v1.39.12) (2026-04-29) ### Performance Improvements diff --git a/packages/core/acme-client/package.json b/packages/core/acme-client/package.json index 30345c6e2..58a9e0d3a 100644 --- a/packages/core/acme-client/package.json +++ b/packages/core/acme-client/package.json @@ -3,7 +3,7 @@ "description": "Simple and unopinionated ACME client", "private": false, "author": "nmorsman", - "version": "1.39.12", + "version": "1.39.13", "type": "module", "module": "./dist/index.js", "main": "./dist/index.js", @@ -18,7 +18,7 @@ "types" ], "dependencies": { - "@certd/basic": "^1.39.12", + "@certd/basic": "^1.39.13", "@peculiar/x509": "^1.11.0", "asn1js": "^3.0.5", "axios": "^1.9.0", @@ -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" }, @@ -74,5 +76,5 @@ "bugs": { "url": "https://github.com/publishlab/node-acme-client/issues" }, - "gitHead": "898bc9b9f2f75df11ea0803b144862ba98b7511a" + "gitHead": "9f7d766cb386b299d4098141f4a47d23e16975e3" } diff --git a/packages/core/basic/CHANGELOG.md b/packages/core/basic/CHANGELOG.md index 8254eddf1..2e9a20997 100644 --- a/packages/core/basic/CHANGELOG.md +++ b/packages/core/basic/CHANGELOG.md @@ -3,6 +3,12 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [1.39.13](https://github.com/certd/certd/compare/v1.39.12...v1.39.13) (2026-05-10) + +### Performance Improvements + +* 重构自动加载模块并优化EAB授权处理 ([4755216](https://github.com/certd/certd/commit/4755216505ad18555a50da9d8008c2207c48df86)) + ## [1.39.12](https://github.com/certd/certd/compare/v1.39.11...v1.39.12) (2026-04-29) ### Performance Improvements diff --git a/packages/core/basic/build.md b/packages/core/basic/build.md index 6d3e00b57..e0ad2f9ca 100644 --- a/packages/core/basic/build.md +++ b/packages/core/basic/build.md @@ -1 +1 @@ -23:06 +00:21 diff --git a/packages/core/basic/package.json b/packages/core/basic/package.json index c9ac8efa7..765a20c65 100644 --- a/packages/core/basic/package.json +++ b/packages/core/basic/package.json @@ -1,7 +1,7 @@ { "name": "@certd/basic", "private": false, - "version": "1.39.12", + "version": "1.39.13", "type": "module", "main": "./dist/index.js", "module": "./dist/index.js", @@ -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", @@ -50,5 +52,5 @@ "tslib": "^2.8.1", "typescript": "^5.4.2" }, - "gitHead": "898bc9b9f2f75df11ea0803b144862ba98b7511a" + "gitHead": "9f7d766cb386b299d4098141f4a47d23e16975e3" } diff --git a/packages/core/basic/src/utils/util.request.ts b/packages/core/basic/src/utils/util.request.ts index 263a59e24..df1edddb7 100644 --- a/packages/core/basic/src/utils/util.request.ts +++ b/packages/core/basic/src/utils/util.request.ts @@ -26,7 +26,7 @@ export class HttpError extends Error { return; } - let message = error?.message || error?.response.statusText || error?.code; + let message = error?.message || error?.response?.statusText || error?.code; if (message && typeof message === "string" && message.indexOf) { for (const key in errorMap) { if (message.indexOf(key) > -1) { @@ -267,7 +267,7 @@ export function createAxiosService({ logger }: { logger: ILogger }) { logger.error(`请求出错:${errorMessage} status:${status},statusText:${error.response?.statusText || error.code},url:${error.config?.url},method:${error.config?.method}。`); logger.error("返回数据:", JSON.stringify(error.response?.data)); if (error.response?.data) { - const message = error.response.data.message || error.response.data.msg || error.response.data.error; + const message = error.response?.data?.message || error.response?.data?.msg || error.response?.data?.error; if (typeof message === "string") { error.message = message; } diff --git a/packages/core/pipeline/CHANGELOG.md b/packages/core/pipeline/CHANGELOG.md index 24869935b..f2c377810 100644 --- a/packages/core/pipeline/CHANGELOG.md +++ b/packages/core/pipeline/CHANGELOG.md @@ -3,6 +3,16 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [1.39.13](https://github.com/certd/certd/compare/v1.39.12...v1.39.13) (2026-05-10) + +### Bug Fixes + +* cnameProvider域名支持设置子域名托管 ([7266af1](https://github.com/certd/certd/commit/7266af17491a98338022cfb18cfedfb93ca6ef8f)) + +### Performance Improvements + +* 重构自动加载模块并优化EAB授权处理 ([4755216](https://github.com/certd/certd/commit/4755216505ad18555a50da9d8008c2207c48df86)) + ## [1.39.12](https://github.com/certd/certd/compare/v1.39.11...v1.39.12) (2026-04-29) ### Performance Improvements diff --git a/packages/core/pipeline/package.json b/packages/core/pipeline/package.json index a2b7e3274..5965582c6 100644 --- a/packages/core/pipeline/package.json +++ b/packages/core/pipeline/package.json @@ -1,7 +1,7 @@ { "name": "@certd/pipeline", "private": false, - "version": "1.39.12", + "version": "1.39.13", "type": "module", "main": "./dist/index.js", "module": "./dist/index.js", @@ -14,13 +14,13 @@ "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" }, "dependencies": { - "@certd/basic": "^1.39.12", - "@certd/plus-core": "^1.39.12", + "@certd/basic": "^1.39.13", + "@certd/plus-core": "^1.39.13", "dayjs": "^1.11.7", "lodash-es": "^4.17.21", "reflect-metadata": "^0.1.13" @@ -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", @@ -47,5 +49,5 @@ "tslib": "^2.8.1", "typescript": "^5.4.2" }, - "gitHead": "898bc9b9f2f75df11ea0803b144862ba98b7511a" + "gitHead": "9f7d766cb386b299d4098141f4a47d23e16975e3" } diff --git a/packages/core/pipeline/src/service/cname.ts b/packages/core/pipeline/src/service/cname.ts index 735222fca..6b9ec9579 100644 --- a/packages/core/pipeline/src/service/cname.ts +++ b/packages/core/pipeline/src/service/cname.ts @@ -3,6 +3,7 @@ import { IAccess } from "../access/index.js"; export type CnameProvider = { id: any; domain: string; + subdomain?: string; title?: string; dnsProviderType?: string; access?: IAccess; diff --git a/packages/libs/lib-huawei/CHANGELOG.md b/packages/libs/lib-huawei/CHANGELOG.md index 203c3e612..1b821c50a 100644 --- a/packages/libs/lib-huawei/CHANGELOG.md +++ b/packages/libs/lib-huawei/CHANGELOG.md @@ -3,6 +3,12 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [1.39.13](https://github.com/certd/certd/compare/v1.39.12...v1.39.13) (2026-05-10) + +### Performance Improvements + +* 重构自动加载模块并优化EAB授权处理 ([4755216](https://github.com/certd/certd/commit/4755216505ad18555a50da9d8008c2207c48df86)) + ## [1.39.12](https://github.com/certd/certd/compare/v1.39.11...v1.39.12) (2026-04-29) **Note:** Version bump only for package @certd/lib-huawei diff --git a/packages/libs/lib-huawei/package.json b/packages/libs/lib-huawei/package.json index f758486af..406e8aa22 100644 --- a/packages/libs/lib-huawei/package.json +++ b/packages/libs/lib-huawei/package.json @@ -1,7 +1,7 @@ { "name": "@certd/lib-huawei", "private": false, - "version": "1.39.12", + "version": "1.39.13", "main": "./dist/bundle.js", "module": "./dist/bundle.js", "types": "./dist/d/index.d.ts", @@ -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,8 +22,10 @@ "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" }, - "gitHead": "898bc9b9f2f75df11ea0803b144862ba98b7511a" + "gitHead": "9f7d766cb386b299d4098141f4a47d23e16975e3" } diff --git a/packages/libs/lib-iframe/CHANGELOG.md b/packages/libs/lib-iframe/CHANGELOG.md index d24196609..0c37758c0 100644 --- a/packages/libs/lib-iframe/CHANGELOG.md +++ b/packages/libs/lib-iframe/CHANGELOG.md @@ -3,6 +3,12 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [1.39.13](https://github.com/certd/certd/compare/v1.39.12...v1.39.13) (2026-05-10) + +### Performance Improvements + +* 重构自动加载模块并优化EAB授权处理 ([4755216](https://github.com/certd/certd/commit/4755216505ad18555a50da9d8008c2207c48df86)) + ## [1.39.12](https://github.com/certd/certd/compare/v1.39.11...v1.39.12) (2026-04-29) **Note:** Version bump only for package @certd/lib-iframe diff --git a/packages/libs/lib-iframe/package.json b/packages/libs/lib-iframe/package.json index 27e2c440c..7f983171e 100644 --- a/packages/libs/lib-iframe/package.json +++ b/packages/libs/lib-iframe/package.json @@ -1,7 +1,7 @@ { "name": "@certd/lib-iframe", "private": false, - "version": "1.39.12", + "version": "1.39.13", "type": "module", "main": "./dist/index.js", "module": "./dist/index.js", @@ -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,13 +24,15 @@ "@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", "typescript": "^5.4.2" }, - "gitHead": "898bc9b9f2f75df11ea0803b144862ba98b7511a" + "gitHead": "9f7d766cb386b299d4098141f4a47d23e16975e3" } diff --git a/packages/libs/lib-jdcloud/CHANGELOG.md b/packages/libs/lib-jdcloud/CHANGELOG.md index 7ece42cef..16f10bb9e 100644 --- a/packages/libs/lib-jdcloud/CHANGELOG.md +++ b/packages/libs/lib-jdcloud/CHANGELOG.md @@ -3,6 +3,12 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [1.39.13](https://github.com/certd/certd/compare/v1.39.12...v1.39.13) (2026-05-10) + +### Performance Improvements + +* 重构自动加载模块并优化EAB授权处理 ([4755216](https://github.com/certd/certd/commit/4755216505ad18555a50da9d8008c2207c48df86)) + ## [1.39.12](https://github.com/certd/certd/compare/v1.39.11...v1.39.12) (2026-04-29) **Note:** Version bump only for package @certd/jdcloud diff --git a/packages/libs/lib-jdcloud/package.json b/packages/libs/lib-jdcloud/package.json index d07e27e41..09ef75930 100644 --- a/packages/libs/lib-jdcloud/package.json +++ b/packages/libs/lib-jdcloud/package.json @@ -1,6 +1,6 @@ { "name": "@certd/jdcloud", - "version": "1.39.12", + "version": "1.39.13", "description": "jdcloud openApi sdk", "main": "./dist/bundle.js", "module": "./dist/bundle.js", @@ -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", @@ -57,5 +58,5 @@ "fetch" ] }, - "gitHead": "898bc9b9f2f75df11ea0803b144862ba98b7511a" + "gitHead": "9f7d766cb386b299d4098141f4a47d23e16975e3" } diff --git a/packages/libs/lib-k8s/CHANGELOG.md b/packages/libs/lib-k8s/CHANGELOG.md index e37a32f10..79d23e2e5 100644 --- a/packages/libs/lib-k8s/CHANGELOG.md +++ b/packages/libs/lib-k8s/CHANGELOG.md @@ -3,6 +3,12 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [1.39.13](https://github.com/certd/certd/compare/v1.39.12...v1.39.13) (2026-05-10) + +### Performance Improvements + +* 重构自动加载模块并优化EAB授权处理 ([4755216](https://github.com/certd/certd/commit/4755216505ad18555a50da9d8008c2207c48df86)) + ## [1.39.12](https://github.com/certd/certd/compare/v1.39.11...v1.39.12) (2026-04-29) **Note:** Version bump only for package @certd/lib-k8s diff --git a/packages/libs/lib-k8s/package.json b/packages/libs/lib-k8s/package.json index 1cb356bd7..71131e00c 100644 --- a/packages/libs/lib-k8s/package.json +++ b/packages/libs/lib-k8s/package.json @@ -1,7 +1,7 @@ { "name": "@certd/lib-k8s", "private": false, - "version": "1.39.12", + "version": "1.39.13", "type": "module", "main": "./dist/index.js", "module": "./dist/index.js", @@ -14,25 +14,27 @@ "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" }, "dependencies": { - "@certd/basic": "^1.39.12", + "@certd/basic": "^1.39.13", "@kubernetes/client-node": "0.21.0" }, "devDependencies": { "@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", "typescript": "^5.4.2" }, - "gitHead": "898bc9b9f2f75df11ea0803b144862ba98b7511a" + "gitHead": "9f7d766cb386b299d4098141f4a47d23e16975e3" } diff --git a/packages/libs/lib-server/CHANGELOG.md b/packages/libs/lib-server/CHANGELOG.md index 1818efe51..939118e3b 100644 --- a/packages/libs/lib-server/CHANGELOG.md +++ b/packages/libs/lib-server/CHANGELOG.md @@ -3,6 +3,13 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [1.39.13](https://github.com/certd/certd/compare/v1.39.12...v1.39.13) (2026-05-10) + +### Performance Improvements + +* **设置:** 添加首页启用开关配置 ([25ad1e6](https://github.com/certd/certd/commit/25ad1e6f861e43288cc8bd90d4903628e6faec29)) +* 重构自动加载模块并优化EAB授权处理 ([4755216](https://github.com/certd/certd/commit/4755216505ad18555a50da9d8008c2207c48df86)) + ## [1.39.12](https://github.com/certd/certd/compare/v1.39.11...v1.39.12) (2026-04-29) ### Performance Improvements diff --git a/packages/libs/lib-server/package.json b/packages/libs/lib-server/package.json index fb3709b06..a68fba0f6 100644 --- a/packages/libs/lib-server/package.json +++ b/packages/libs/lib-server/package.json @@ -1,6 +1,6 @@ { "name": "@certd/lib-server", - "version": "1.39.12", + "version": "1.39.13", "description": "midway with flyway, sql upgrade way ", "private": false, "type": "module", @@ -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", @@ -29,11 +29,11 @@ ], "license": "AGPL", "dependencies": { - "@certd/acme-client": "^1.39.12", - "@certd/basic": "^1.39.12", - "@certd/pipeline": "^1.39.12", - "@certd/plugin-lib": "^1.39.12", - "@certd/plus-core": "^1.39.12", + "@certd/acme-client": "^1.39.13", + "@certd/basic": "^1.39.13", + "@certd/pipeline": "^1.39.13", + "@certd/plugin-lib": "^1.39.13", + "@certd/plus-core": "^1.39.13", "@midwayjs/cache": "3.14.0", "@midwayjs/core": "3.20.11", "@midwayjs/i18n": "3.20.13", @@ -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", @@ -68,5 +69,5 @@ "typeorm": "^0.3.11", "typescript": "^5.4.2" }, - "gitHead": "898bc9b9f2f75df11ea0803b144862ba98b7511a" + "gitHead": "9f7d766cb386b299d4098141f4a47d23e16975e3" } diff --git a/packages/libs/lib-server/src/user/access/service/access-service.test.ts b/packages/libs/lib-server/src/user/access/service/access-service.test.ts new file mode 100644 index 000000000..4e9758eb4 --- /dev/null +++ b/packages/libs/lib-server/src/user/access/service/access-service.test.ts @@ -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", + }); + }); +}); diff --git a/packages/libs/lib-server/src/user/access/service/access-service.ts b/packages/libs/lib-server/src/user/access/service/access-service.ts index f2d92ee0f..8bbb81e90 100644 --- a/packages/libs/lib-server/src/user/access/service/access-service.ts +++ b/packages/libs/lib-server/src/user/access/service/access-service.ts @@ -123,6 +123,25 @@ export class AccessService extends BaseService { 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) { diff --git a/packages/libs/midway-flyway-js/CHANGELOG.md b/packages/libs/midway-flyway-js/CHANGELOG.md index 1168d191d..020e806d5 100644 --- a/packages/libs/midway-flyway-js/CHANGELOG.md +++ b/packages/libs/midway-flyway-js/CHANGELOG.md @@ -3,6 +3,12 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [1.39.13](https://github.com/certd/certd/compare/v1.39.12...v1.39.13) (2026-05-10) + +### Performance Improvements + +* 重构自动加载模块并优化EAB授权处理 ([4755216](https://github.com/certd/certd/commit/4755216505ad18555a50da9d8008c2207c48df86)) + ## [1.39.12](https://github.com/certd/certd/compare/v1.39.11...v1.39.12) (2026-04-29) **Note:** Version bump only for package @certd/midway-flyway-js diff --git a/packages/libs/midway-flyway-js/package.json b/packages/libs/midway-flyway-js/package.json index 3867f0cbb..2aa6d65cc 100644 --- a/packages/libs/midway-flyway-js/package.json +++ b/packages/libs/midway-flyway-js/package.json @@ -1,6 +1,6 @@ { "name": "@certd/midway-flyway-js", - "version": "1.39.12", + "version": "1.39.13", "description": "midway with flyway, sql upgrade way ", "private": false, "type": "module", @@ -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,16 +36,18 @@ "@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", "typeorm": "^0.3.11", "typescript": "^5.4.2" }, - "gitHead": "898bc9b9f2f75df11ea0803b144862ba98b7511a" + "gitHead": "9f7d766cb386b299d4098141f4a47d23e16975e3" } diff --git a/packages/plugins/plugin-cert/CHANGELOG.md b/packages/plugins/plugin-cert/CHANGELOG.md index 6315c4ab7..48675d1c3 100644 --- a/packages/plugins/plugin-cert/CHANGELOG.md +++ b/packages/plugins/plugin-cert/CHANGELOG.md @@ -3,6 +3,12 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [1.39.13](https://github.com/certd/certd/compare/v1.39.12...v1.39.13) (2026-05-10) + +### Performance Improvements + +* 重构自动加载模块并优化EAB授权处理 ([4755216](https://github.com/certd/certd/commit/4755216505ad18555a50da9d8008c2207c48df86)) + ## [1.39.12](https://github.com/certd/certd/compare/v1.39.11...v1.39.12) (2026-04-29) **Note:** Version bump only for package @certd/plugin-cert diff --git a/packages/plugins/plugin-cert/package.json b/packages/plugins/plugin-cert/package.json index c3f8310a5..688e225dd 100644 --- a/packages/plugins/plugin-cert/package.json +++ b/packages/plugins/plugin-cert/package.json @@ -1,7 +1,7 @@ { "name": "@certd/plugin-cert", "private": false, - "version": "1.39.12", + "version": "1.39.13", "type": "module", "main": "./dist/index.js", "types": "./dist/index.d.ts", @@ -13,15 +13,15 @@ "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" }, "dependencies": { - "@certd/acme-client": "^1.39.12", - "@certd/basic": "^1.39.12", - "@certd/pipeline": "^1.39.12", - "@certd/plugin-lib": "^1.39.12", + "@certd/acme-client": "^1.39.13", + "@certd/basic": "^1.39.13", + "@certd/pipeline": "^1.39.13", + "@certd/plugin-lib": "^1.39.13", "psl": "^1.9.0", "punycode.js": "^2.3.1" }, @@ -31,13 +31,15 @@ "@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", "typescript": "^5.4.2" }, - "gitHead": "898bc9b9f2f75df11ea0803b144862ba98b7511a" + "gitHead": "9f7d766cb386b299d4098141f4a47d23e16975e3" } diff --git a/packages/plugins/plugin-lib/CHANGELOG.md b/packages/plugins/plugin-lib/CHANGELOG.md index a5cc856e8..b9f5121e8 100644 --- a/packages/plugins/plugin-lib/CHANGELOG.md +++ b/packages/plugins/plugin-lib/CHANGELOG.md @@ -3,6 +3,12 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [1.39.13](https://github.com/certd/certd/compare/v1.39.12...v1.39.13) (2026-05-10) + +### Performance Improvements + +* 重构自动加载模块并优化EAB授权处理 ([4755216](https://github.com/certd/certd/commit/4755216505ad18555a50da9d8008c2207c48df86)) + ## [1.39.12](https://github.com/certd/certd/compare/v1.39.11...v1.39.12) (2026-04-29) **Note:** Version bump only for package @certd/plugin-lib diff --git a/packages/plugins/plugin-lib/package.json b/packages/plugins/plugin-lib/package.json index a55205377..62b4ba3b8 100644 --- a/packages/plugins/plugin-lib/package.json +++ b/packages/plugins/plugin-lib/package.json @@ -1,7 +1,7 @@ { "name": "@certd/plugin-lib", "private": false, - "version": "1.39.12", + "version": "1.39.13", "type": "module", "main": "./dist/index.js", "types": "./dist/index.d.ts", @@ -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" }, @@ -23,10 +23,10 @@ "@alicloud/pop-core": "^1.7.10", "@alicloud/tea-util": "^1.4.11", "@aws-sdk/client-s3": "^3.964.0", - "@certd/acme-client": "^1.39.12", - "@certd/basic": "^1.39.12", - "@certd/pipeline": "^1.39.12", - "@certd/plus-core": "^1.39.12", + "@certd/acme-client": "^1.39.13", + "@certd/basic": "^1.39.13", + "@certd/pipeline": "^1.39.13", + "@certd/plus-core": "^1.39.13", "@kubernetes/client-node": "0.21.0", "ali-oss": "^6.22.0", "basic-ftp": "^5.0.5", @@ -50,14 +50,16 @@ "@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", "tslib": "^2.8.1", "typescript": "^5.4.2" }, - "gitHead": "898bc9b9f2f75df11ea0803b144862ba98b7511a" + "gitHead": "9f7d766cb386b299d4098141f4a47d23e16975e3" } diff --git a/packages/ui/Dockerfile b/packages/ui/Dockerfile index 05d5dd20c..64c5d1952 100644 --- a/packages/ui/Dockerfile +++ b/packages/ui/Dockerfile @@ -1,4 +1,4 @@ -FROM node:22-alpine AS builder +FROM node:22-alpine3.21 AS builder # RUN apk add build-base # RUN wget -O - https://github.com/jemalloc/jemalloc/releases/download/5.3.0/jemalloc-5.3.0.tar.bz2 | tar -xj && \ @@ -18,9 +18,14 @@ RUN npm install -g pnpm@10.33.4 RUN cp /workspace/certd-client/dist/* /workspace/certd-server/public/ -rf RUN cd /workspace/certd-server && pnpm install && npm run build-on-docker +# RUN cd /workspace/certd-server && \ +# pnpm install --ignore-scripts && \ +# yes | pnpm approve-builds && \ +# pnpm rebuild && \ +# npm run build-on-docker -FROM node:22-alpine +FROM node:22-alpine3.21 EXPOSE 7001 EXPOSE 7002 diff --git a/packages/ui/certd-client/CHANGELOG.md b/packages/ui/certd-client/CHANGELOG.md index fb0f52515..247bb46d8 100644 --- a/packages/ui/certd-client/CHANGELOG.md +++ b/packages/ui/certd-client/CHANGELOG.md @@ -3,6 +3,21 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [1.39.13](https://github.com/certd/certd/compare/v1.39.12...v1.39.13) (2026-05-10) + +### Bug Fixes + +* cnameProvider域名支持设置子域名托管 ([7266af1](https://github.com/certd/certd/commit/7266af17491a98338022cfb18cfedfb93ca6ef8f)) + +### Performance Improvements + +* **设置:** 添加首页启用开关配置 ([25ad1e6](https://github.com/certd/certd/commit/25ad1e6f861e43288cc8bd90d4903628e6faec29)) +* **用户资料:** 新增手机号邮箱绑定功能 ([e0eb0e2](https://github.com/certd/certd/commit/e0eb0e21f6dae24b639c944f9aba2c90496ab1c0)) +* 域名注册过期时间获取再次优化 ([91a1b97](https://github.com/certd/certd/commit/91a1b9755066bf280e194dabf7c3a9f936e2643f)) +* **证书流水线:** 添加批量更新证书申请参数功能 ([63be1c1](https://github.com/certd/certd/commit/63be1c1cbd9b09a3b48f26130c296b1cedcca1ac)) +* 重构自动加载模块并优化EAB授权处理 ([4755216](https://github.com/certd/certd/commit/4755216505ad18555a50da9d8008c2207c48df86)) +* **domain:** 添加域名过期时间同步进度显示功能 ([9d2937d](https://github.com/certd/certd/commit/9d2937dd4b14ffab73e9b096edd2aa8539811182)) + ## [1.39.12](https://github.com/certd/certd/compare/v1.39.11...v1.39.12) (2026-04-29) ### Bug Fixes diff --git a/packages/ui/certd-client/package.json b/packages/ui/certd-client/package.json index c2980ed5f..532acf190 100644 --- a/packages/ui/certd-client/package.json +++ b/packages/ui/certd-client/package.json @@ -1,6 +1,6 @@ { "name": "@certd/ui-client", - "version": "1.39.12", + "version": "1.39.13", "private": true, "scripts": { "dev": "vite --open", @@ -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", @@ -107,8 +106,8 @@ "zod-defaults": "^0.1.3" }, "devDependencies": { - "@certd/lib-iframe": "^1.39.12", - "@certd/pipeline": "^1.39.12", + "@certd/lib-iframe": "^1.39.13", + "@certd/pipeline": "^1.39.13", "@rollup/plugin-commonjs": "^25.0.7", "@rollup/plugin-node-resolve": "^15.2.3", "@types/chai": "^4.3.12", @@ -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", diff --git a/packages/ui/certd-client/src/components/plugins/common/refresh-input.vue b/packages/ui/certd-client/src/components/plugins/common/refresh-input.vue new file mode 100644 index 000000000..8139a8c57 --- /dev/null +++ b/packages/ui/certd-client/src/components/plugins/common/refresh-input.vue @@ -0,0 +1,106 @@ + + + + + diff --git a/packages/ui/certd-client/src/components/plugins/index.ts b/packages/ui/certd-client/src/components/plugins/index.ts index 117d66864..393e2d10d 100644 --- a/packages/ui/certd-client/src/components/plugins/index.ts +++ b/packages/ui/certd-client/src/components/plugins/index.ts @@ -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); diff --git a/packages/ui/certd-client/src/locales/langs/en-US/certd/sys-cname.ts b/packages/ui/certd-client/src/locales/langs/en-US/certd/sys-cname.ts index 66c1faaff..e23e36aae 100644 --- a/packages/ui/certd-client/src/locales/langs/en-US/certd/sys-cname.ts +++ b/packages/ui/certd-client/src/locales/langs/en-US/certd/sys-cname.ts @@ -1,13 +1,16 @@ export default { cnameTitle: "CNAME Service Configuration", cnameDescription: - "The domain name configured here serves as a proxy for verifying other domains. When other domains apply for certificates, they map to this domain via CNAME for ownership verification. The advantage is that any domain can apply for a certificate this way without providing an AccessSecret.", + "The domain name configured here serves as a proxy for verifying other domains. When other domains apply for certificates, they map to this domain via CNAME for ownership verification. The advantage is that any domain can apply for a certificate this way without providing an AccessSecret.", cnameLinkText: "CNAME principle and usage instructions", cnameDomain: "CNAME Domain", cnameDomainPlaceholder: "cname.handsfree.work", cnameDomainHelper: - "Requires a domain registered with a DNS provider on the right (or you can transfer other domain DNS servers here).\nOnce the CNAME domain is set, it cannot be changed. It is recommended to use a first-level subdomain.", + "Requires a domain registered with a DNS provider on the right (or you can transfer other domain DNS servers here).\nOnce the CNAME domain is set, it cannot be changed. It is recommended to use a first-level subdomain.", cnameDomainPattern: "Domain name cannot contain *", + cnameProviderSubdomain: "Delegated Subdomain", + cnameProviderSubdomainPlaceholder: "sub.example.com", + cnameProviderSubdomainHelper: "Fill this when the CNAME domain is hosted under a delegated subdomain, for example CNAME domain cname.sub.example.com and DNS zone sub.example.com.", dnsProvider: "DNS Provider", dnsProviderAuthorization: "DNS Provider Authorization", }; diff --git a/packages/ui/certd-client/src/locales/langs/zh-CN/certd/sys-cname.ts b/packages/ui/certd-client/src/locales/langs/zh-CN/certd/sys-cname.ts index 0da608a2b..03401e66f 100644 --- a/packages/ui/certd-client/src/locales/langs/zh-CN/certd/sys-cname.ts +++ b/packages/ui/certd-client/src/locales/langs/zh-CN/certd/sys-cname.ts @@ -6,6 +6,9 @@ export default { cnameDomainPlaceholder: "cname.handsfree.work", cnameDomainHelper: "需要一个右边DNS提供商注册的域名(也可以将其他域名的dns服务器转移到这几家来)。\nCNAME域名一旦确定不可修改,建议使用一级子域名", cnameDomainPattern: "域名不能使用星号", + cnameProviderSubdomain: "托管子域名", + cnameProviderSubdomainPlaceholder: "sub.example.com", + cnameProviderSubdomainHelper: "当CNAME域名本身托管在子域名下时填写,例如 CNAME域名为 cname.sub.example.com,实际DNS托管域为 sub.example.com", dnsProvider: "DNS提供商", dnsProviderAuthorization: "DNS提供商授权", }; diff --git a/packages/ui/certd-client/src/views/certd/mine/api.ts b/packages/ui/certd-client/src/views/certd/mine/api.ts index 9ce8648ad..c30fb7d78 100644 --- a/packages/ui/certd-client/src/views/certd/mine/api.ts +++ b/packages/ui/certd-client/src/views/certd/mine/api.ts @@ -23,6 +23,37 @@ export async function UpdateProfile(form: any) { }); } +export async function GetContactCapability() { + return await request({ + url: "/mine/contact/capability", + method: "POST", + }); +} + +export async function UpdateMobile(form: any) { + return await request({ + url: "/mine/contact/mobile", + method: "POST", + data: form, + }); +} + +export async function VerifyContactIdentity(form: any) { + return await request({ + url: "/mine/contact/verifyIdentity", + method: "POST", + data: form, + }); +} + +export async function UpdateEmail(form: any) { + return await request({ + url: "/mine/contact/email", + method: "POST", + data: form, + }); +} + export async function GetOauthBounds() { return await request({ url: "/oauth/bounds", diff --git a/packages/ui/certd-client/src/views/certd/mine/contact-code-input.tsx b/packages/ui/certd-client/src/views/certd/mine/contact-code-input.tsx new file mode 100644 index 000000000..c1e5cef84 --- /dev/null +++ b/packages/ui/certd-client/src/views/certd/mine/contact-code-input.tsx @@ -0,0 +1,31 @@ +import { defineComponent } from "vue"; +import SmsCode from "/@/views/framework/login/sms-code.vue"; +import EmailCode from "/@/views/framework/register/email-code.vue"; + +export const ContactCodeInput = defineComponent({ + name: "ContactCodeInput", + props: { + modelValue: { + type: String, + default: "", + }, + form: { + type: Object, + required: true, + }, + type: { + type: String, + required: true, + }, + }, + emits: ["update:modelValue"], + setup(props, { emit }) { + const onChange = (value: string) => emit("update:modelValue", value); + return () => { + if (props.type === "email") { + return ; + } + return ; + }; + }, +}); diff --git a/packages/ui/certd-client/src/views/certd/mine/identity-code-input.tsx b/packages/ui/certd-client/src/views/certd/mine/identity-code-input.tsx new file mode 100644 index 000000000..9fde09b17 --- /dev/null +++ b/packages/ui/certd-client/src/views/certd/mine/identity-code-input.tsx @@ -0,0 +1,33 @@ +import { defineComponent } from "vue"; +import SmsCode from "/@/views/framework/login/sms-code.vue"; +import EmailCode from "/@/views/framework/register/email-code.vue"; + +export const IdentityCodeInput = defineComponent({ + name: "IdentityCodeInput", + props: { + modelValue: { + type: String, + default: "", + }, + form: { + type: Object, + required: true, + }, + userInfo: { + type: Object, + required: true, + }, + }, + emits: ["update:modelValue"], + setup(props, { emit }) { + const onChange = (value: string) => emit("update:modelValue", value); + return () => { + if (props.form.identityType === "email") { + return ; + } + return ( + + ); + }; + }, +}); diff --git a/packages/ui/certd-client/src/views/certd/mine/use.tsx b/packages/ui/certd-client/src/views/certd/mine/use.tsx index f5e4e536e..d95b37ff0 100644 --- a/packages/ui/certd-client/src/views/certd/mine/use.tsx +++ b/packages/ui/certd-client/src/views/certd/mine/use.tsx @@ -1,20 +1,22 @@ // useUserProfile, 获取 openEditProfileDialog ,参考 useTemplate方法 -import { useFormWrapper } from "@fast-crud/fast-crud"; -import { ref } from "vue"; -import { cloneDeep, merge } from "lodash-es"; +import { compute, dict } from "@fast-crud/fast-crud"; // 假设的 API 导入 import * as userProfileApi from "./api"; import { useUserStore } from "/@/store/user"; import { useI18n } from "/src/locales"; +import CaptchaInput from "/@/components/captcha/captcha-input.vue"; +import { message } from "ant-design-vue"; +import { ContactCodeInput } from "./contact-code-input"; +import { IdentityCodeInput } from "./identity-code-input"; +import { useFormDialog } from "/@/use/use-dialog"; /** * 获取用户资料编辑相关功能 * @returns {{openEditProfileDialog: openEditProfileDialog}} */ export function useUserProfile() { - const { openCrudFormDialog } = useFormWrapper(); - const wrapperRef = ref(); + const { openFormDialog } = useFormDialog(); async function openEditProfileDialog(req: { onUpdated?: (ctx: any) => void }) { const detail = await userProfileApi.getMineInfo(); if (!detail) { @@ -24,31 +26,28 @@ export function useUserProfile() { const { t } = useI18n(); const userStore = useUserStore(); - const userProfileFormRef = ref(); - async function doSubmit(opts: { form: any }) { - const form = opts.form; + async function doSubmit(form: any) { const { id } = await userProfileApi.UpdateProfile(form); if (req.onUpdated) { req.onUpdated({ id }); } } - const crudOptions: any = { - form: { - doSubmit, - wrapper: { - title: `编辑用户资料`, - width: 1100, - onOpened(opts: { form: any }) { - merge(opts.form, detail); - }, - }, + await openFormDialog({ + title: `编辑用户资料`, + wrapper: { + width: 600, }, + initialForm: detail, + onSubmit: doSubmit, columns: { nickName: { title: t("certd.nickName"), type: "text", form: { + col: { + span: 24, + }, component: { placeholder: t("certd.nickName"), }, @@ -71,6 +70,9 @@ export function useUserProfile() { }, }, form: { + col: { + span: 24, + }, component: { vModel: "modelValue", valueType: "key", @@ -98,10 +100,7 @@ export function useUserProfile() { }, }, }, - }; - - const wrapper = await openCrudFormDialog({ crudOptions }); - wrapperRef.value = wrapper; + }); } return { @@ -110,26 +109,20 @@ export function useUserProfile() { } export function usePasskeyRegister() { - const { openCrudFormDialog } = useFormWrapper(); - const wrapperRef = ref(); + const { openFormDialog } = useFormDialog(); async function openRegisterDialog(req: { onSubmit?: (ctx: any) => void }) { const { t } = useI18n(); - const userStore = useUserStore(); - const deviceNameRef = ref(); - - const crudOptions: any = { - form: { - wrapper: { - title: t("authentication.registerPasskey"), - width: 500, - onOpened(opts: { form: any }) { - opts.form.deviceName = ""; - }, - }, - onSubmit: req.onSubmit, - afterSubmit: null, - onSuccess: null, + await openFormDialog({ + title: t("authentication.registerPasskey"), + wrapper: { + width: 500, + }, + initialForm: { + deviceName: "", + }, + onSubmit: async (form: any) => { + await req.onSubmit?.({ form }); }, columns: { deviceName: { @@ -147,15 +140,229 @@ export function usePasskeyRegister() { }, }, }, - }; - - const wrapper = await openCrudFormDialog({ crudOptions }); - wrapperRef.value = wrapper; - - return wrapper; + }); } return { openRegisterDialog, }; } + +export function useContactBind() { + const { openFormDialog } = useFormDialog(); + + async function openContactBindDialog(req: { type: "mobile" | "email"; userInfo: any; contactCapability: { smsEnabled?: boolean }; onUpdated?: () => Promise | void }) { + const methods = [{ label: "密码", value: "password" }]; + if (req.userInfo.email) { + methods.push({ label: "邮箱", value: "email" }); + } + if (req.contactCapability.smsEnabled && req.userInfo.mobile) { + methods.push({ label: "手机号", value: "mobile" }); + } + + async function openChangeDialog(identityValidationCode: string) { + const isMobile = req.type === "mobile"; + await openFormDialog({ + title: isMobile ? (req.userInfo.mobile ? "修改手机号" : "绑定手机号") : req.userInfo.email ? "修改邮箱" : "绑定邮箱", + wrapper: { + width: 560, + }, + initialForm: { + phoneCode: req.userInfo.phoneCode || "86", + mobile: req.userInfo.mobile || "", + email: req.userInfo.email || "", + contactCaptcha: null, + contactValidateCode: "", + }, + async onSubmit(form: any) { + if (isMobile) { + await userProfileApi.UpdateMobile({ + phoneCode: form.phoneCode, + mobile: form.mobile, + validateCode: form.contactValidateCode, + identityValidationCode, + }); + } else { + await userProfileApi.UpdateEmail({ + email: form.email, + validateCode: form.contactValidateCode, + identityValidationCode, + }); + } + message.success("绑定信息已更新"); + await req.onUpdated?.(); + }, + columns: { + phoneCode: { + title: "区号", + type: "text", + form: { + col: { + span: 24, + }, + show: isMobile, + component: { + placeholder: "区号", + }, + rules: [{ required: isMobile, message: "请输入区号" }], + }, + }, + mobile: { + title: "手机号", + type: "text", + form: { + col: { + span: 24, + }, + show: isMobile, + component: { + placeholder: "请输入手机号", + }, + rules: [ + { required: isMobile, message: "请输入手机号" }, + { pattern: /^\d{4,20}$/, message: "请输入正确的手机号" }, + ], + }, + }, + email: { + title: "邮箱", + type: "text", + form: { + col: { + span: 24, + }, + show: !isMobile, + component: { + placeholder: "请输入邮箱", + }, + rules: [ + { required: !isMobile, message: "请输入邮箱" }, + { type: "email", message: "请输入正确的邮箱" }, + ], + }, + }, + contactCaptcha: { + title: "图形验证码", + form: { + col: { + span: 24, + }, + component: { + name: CaptchaInput, + vModel: "modelValue", + }, + rules: [{ required: true, message: "请完成图形验证码" }], + }, + }, + contactValidateCode: { + title: isMobile ? "新手机号验证码" : "新邮箱验证码", + form: { + col: { + span: 24, + }, + component: { + name: ContactCodeInput, + vModel: "modelValue", + form: compute(({ form }) => form), + type: req.type, + }, + rules: [{ required: true, message: "请输入验证码" }], + }, + }, + }, + }); + } + + await openFormDialog({ + title: "验证本人操作", + wrapper: { + width: 520, + }, + initialForm: { + identityType: "password", + identityPassword: "", + identityCaptcha: null, + identityValidateCode: "", + }, + async onSubmit(form: any) { + const res = await userProfileApi.VerifyContactIdentity({ + identityType: form.identityType, + identityPassword: form.identityPassword, + identityValidateCode: form.identityValidateCode, + }); + await openChangeDialog(res.validationCode); + }, + columns: { + identityType: { + title: "验证方式", + form: { + col: { + span: 24, + }, + component: { + name: "fs-dict-radio", + vModel: "value", + dict: dict({ + data: methods, + }), + }, + rules: [{ required: true, message: "请选择验证方式" }], + valueChange({ form }: { form: any }) { + form.identityPassword = ""; + form.identityCaptcha = null; + form.identityValidateCode = ""; + }, + }, + }, + identityPassword: { + title: "登录密码", + type: "password", + form: { + col: { + span: 24, + }, + show: compute(({ form }) => form.identityType === "password"), + component: { + placeholder: "请输入登录密码", + }, + rules: [{ required: true, message: "请输入登录密码" }], + }, + }, + identityCaptcha: { + title: "图形验证码", + form: { + col: { + span: 24, + }, + show: compute(({ form }) => form.identityType !== "password"), + component: { + name: CaptchaInput, + vModel: "modelValue", + }, + rules: [{ required: true, message: "请完成图形验证码" }], + }, + }, + identityValidateCode: { + title: "验证码", + form: { + col: { + span: 24, + }, + show: compute(({ form }) => form.identityType !== "password"), + component: { + name: IdentityCodeInput, + vModel: "modelValue", + form: compute(({ form }) => form), + userInfo: req.userInfo, + }, + rules: [{ required: true, message: "请输入验证码" }], + }, + }, + }, + }); + } + + return { + openContactBindDialog, + }; +} diff --git a/packages/ui/certd-client/src/views/certd/mine/user-profile.vue b/packages/ui/certd-client/src/views/certd/mine/user-profile.vue index 290fc0a4e..c7160cae3 100644 --- a/packages/ui/certd-client/src/views/certd/mine/user-profile.vue +++ b/packages/ui/certd-client/src/views/certd/mine/user-profile.vue @@ -13,32 +13,41 @@ {{ userInfo.username }} + + +
- - {{ t("authentication.updateProfile") }} - @@ -139,7 +148,7 @@ import * as api from "./api"; import { computed, onMounted, Ref, ref } from "vue"; import ChangePasswordButton from "/@/views/certd/mine/change-password-button.vue"; import { useI18n } from "/src/locales"; -import { useUserProfile } from "./use"; +import { useContactBind, useUserProfile } from "./use"; import { usePasskeyRegister } from "./use"; import { message, Modal, notification } from "ant-design-vue"; import { useSettingStore } from "/@/store/settings"; @@ -160,6 +169,9 @@ const settingStore = useSettingStore(); const userInfo: Ref = ref({}); const passkeys = ref([]); const passkeySupported = ref(false); +const contactCapability = ref({ + smsEnabled: false, +}); const getUserInfo = async () => { userInfo.value = await api.getMineInfo(); @@ -177,6 +189,7 @@ function doUpdate() { openEditProfileDialog({ onUpdated: async () => { await getUserInfo(); + userStore.setUserInfo(userInfo.value); }, }); } @@ -237,6 +250,23 @@ async function loadPasskeys() { } } +async function loadContactCapability() { + contactCapability.value = await api.GetContactCapability(); +} + +const { openContactBindDialog } = useContactBind(); +async function openBindContact(type: "mobile" | "email") { + await openContactBindDialog({ + type, + userInfo: userInfo.value, + contactCapability: contactCapability.value, + onUpdated: async () => { + await getUserInfo(); + userStore.setUserInfo(userInfo.value); + }, + }); +} + async function unbindPasskey(id: number) { Modal.confirm({ title: "确认解绑吗?", @@ -366,6 +396,7 @@ const userAvatar = computed(() => { onMounted(async () => { await getUserInfo(); + await loadContactCapability(); await loadOauthBounds(); await loadOauthProviders(); await loadPasskeys(); @@ -567,6 +598,24 @@ onMounted(async () => { flex-shrink: 0; } + .avatar-edit-btn { + position: absolute; + right: 2px; + bottom: 2px; + display: inline-flex; + align-items: center; + justify-content: center; + width: 28px; + height: 28px; + min-width: 28px; + padding: 0; + color: #667eea; + background: #ffffff; + border: 1px solid #e5e7eb; + border-radius: 50%; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.12); + } + .user-avatar { border: 4px solid #ffffff; box-shadow: 0 4px 15px rgba(0, 0, 0, 0.1); @@ -613,6 +662,18 @@ onMounted(async () => { font-size: 13px; } + .detail-edit-btn { + display: inline-flex; + align-items: center; + justify-content: center; + width: 20px; + height: 20px; + min-width: 20px; + margin: -2px -6px -2px 0; + padding: 0; + color: #667eea; + } + .tag-icon { font-size: 14px; } @@ -863,18 +924,84 @@ onMounted(async () => { } @media (max-width: 768px) { + .card-header { + padding: 32px 16px; + } + .header-content { flex-direction: column; text-align: center; + gap: 18px; + } + + .avatar-wrapper { + margin-bottom: 2px; + } + + .user-avatar { + width: 88px !important; + height: 88px !important; + line-height: 88px !important; + } + + .avatar-edit-btn { + right: -2px; + bottom: 0; + } + + .user-name { + justify-content: center; + margin-bottom: 14px; + font-size: 22px; + gap: 6px; } .user-details { justify-content: center; + flex-direction: column; + align-items: center; + gap: 6px; + width: 100%; + } + + .detail-tag { + justify-content: flex-start; + width: min(230px, calc(100vw - 96px)); + min-height: 34px; + padding: 5px 8px 5px 12px; + border-radius: 12px; + white-space: nowrap; + margin-right: 0; + text-align: left; + } + + .tag-icon { + flex: 0 0 auto; + } + + .tag-text { + flex: 1; + min-width: 0; + overflow: hidden; + text-overflow: ellipsis; + } + + .detail-edit-btn { + margin: 0 -4px 0 2px; + flex: 0 0 auto; } .action-buttons { justify-content: center; width: 100%; + gap: 10px; + margin-top: 2px; + } + + .action-buttons :deep(.ant-btn) { + min-width: 90px; + height: 32px; + padding: 4px 12px; } } } diff --git a/packages/ui/certd-client/src/views/sys/cname/provider/crud.tsx b/packages/ui/certd-client/src/views/sys/cname/provider/crud.tsx index 6cd29a7ec..4f98606f0 100644 --- a/packages/ui/certd-client/src/views/sys/cname/provider/crud.tsx +++ b/packages/ui/certd-client/src/views/sys/cname/provider/crud.tsx @@ -97,6 +97,21 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat width: 200, }, }, + subdomain: { + title: t("certd.cnameProviderSubdomain"), + type: "text", + form: { + component: { + placeholder: t("certd.cnameProviderSubdomainPlaceholder"), + }, + helper: t("certd.cnameProviderSubdomainHelper"), + rules: [{ pattern: /^[^*]+$/, message: t("certd.cnameDomainPattern") }], + }, + column: { + width: 200, + show: false, + }, + }, dnsProviderType: { title: t("certd.dnsProvider"), type: "dict-select", diff --git a/packages/ui/certd-server/CHANGELOG.md b/packages/ui/certd-server/CHANGELOG.md index af7235cbe..e703905c5 100644 --- a/packages/ui/certd-server/CHANGELOG.md +++ b/packages/ui/certd-server/CHANGELOG.md @@ -3,6 +3,23 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [1.39.13](https://github.com/certd/certd/compare/v1.39.12...v1.39.13) (2026-05-10) + +### Bug Fixes + +* **aliyun-access:** 添加阿里云密钥校验失败的错误处理 ([b75c625](https://github.com/certd/certd/commit/b75c625ddcc0b3110699d8e6175681ef157b25df)) +* cnameProvider域名支持设置子域名托管 ([7266af1](https://github.com/certd/certd/commit/7266af17491a98338022cfb18cfedfb93ca6ef8f)) +* **plugin-aliyun:** 过滤非CAS证书并优化日志信息 ([c4b01da](https://github.com/certd/certd/commit/c4b01da384bc40a241a673ea8bc01ca733c04d83)) + +### Performance Improvements + +* **用户资料:** 新增手机号邮箱绑定功能 ([e0eb0e2](https://github.com/certd/certd/commit/e0eb0e21f6dae24b639c944f9aba2c90496ab1c0)) +* 域名注册过期时间获取再次优化 ([91a1b97](https://github.com/certd/certd/commit/91a1b9755066bf280e194dabf7c3a9f936e2643f)) +* **证书流水线:** 添加批量更新证书申请参数功能 ([63be1c1](https://github.com/certd/certd/commit/63be1c1cbd9b09a3b48f26130c296b1cedcca1ac)) +* 支持火山云vke ([bb46cb0](https://github.com/certd/certd/commit/bb46cb08f71f6ae921543f7e4a6c5f4e0190556e)) +* 重构自动加载模块并优化EAB授权处理 ([4755216](https://github.com/certd/certd/commit/4755216505ad18555a50da9d8008c2207c48df86)) +* **plugin-volcengine:** 支持火山引擎VKE部署插件 ([b8a64a6](https://github.com/certd/certd/commit/b8a64a6b5bf3691a47177de42bc49b798e795feb)) + ## [1.39.12](https://github.com/certd/certd/compare/v1.39.11...v1.39.12) (2026-04-29) ### Bug Fixes diff --git a/packages/ui/certd-server/db/migration-mysql/v10043__cname_provider_subdomain.sql b/packages/ui/certd-server/db/migration-mysql/v10043__cname_provider_subdomain.sql new file mode 100644 index 000000000..c8055420b --- /dev/null +++ b/packages/ui/certd-server/db/migration-mysql/v10043__cname_provider_subdomain.sql @@ -0,0 +1 @@ +ALTER TABLE cd_cname_provider ADD COLUMN subdomain varchar(100); diff --git a/packages/ui/certd-server/db/migration-pg/v10043__cname_provider_subdomain.sql b/packages/ui/certd-server/db/migration-pg/v10043__cname_provider_subdomain.sql new file mode 100644 index 000000000..c8055420b --- /dev/null +++ b/packages/ui/certd-server/db/migration-pg/v10043__cname_provider_subdomain.sql @@ -0,0 +1 @@ +ALTER TABLE cd_cname_provider ADD COLUMN subdomain varchar(100); diff --git a/packages/ui/certd-server/db/migration/v10043__cname_provider_subdomain.sql b/packages/ui/certd-server/db/migration/v10043__cname_provider_subdomain.sql new file mode 100644 index 000000000..c8055420b --- /dev/null +++ b/packages/ui/certd-server/db/migration/v10043__cname_provider_subdomain.sql @@ -0,0 +1 @@ +ALTER TABLE cd_cname_provider ADD COLUMN subdomain varchar(100); diff --git a/packages/ui/certd-server/metadata/access_eab.yaml b/packages/ui/certd-server/metadata/access_eab.yaml index 2bfcb4e15..d2168c859 100644 --- a/packages/ui/certd-server/metadata/access_eab.yaml +++ b/packages/ui/certd-server/metadata/access_eab.yaml @@ -3,6 +3,26 @@ title: EAB授权 desc: ZeroSSL证书申请需要EAB授权 icon: ic:outline-lock input: + eabType: + 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 + icon: la:expeditedssl + helper: 请选择EAB类型 + required: true + encrypt: false kid: title: KID component: @@ -24,8 +44,20 @@ input: rules: - type: email message: 请输入正确的邮箱 - helper: Google的EAB申请证书,更换邮箱会导致EAB失效,可以在此处绑定一个邮箱避免此问题 + helper: 绑定一个邮箱,避免失效 required: true + accountKey: + title: ACME账号私钥 + component: + name: refresh-input + action: GenerateAccountKey + buttonText: 生成 + successMessage: 账号私钥已生成,请保存授权配置 + required: true + helper: |- + 如果修改了KID,请点击生成重新生成账号私钥 + 注意:google的EAB只能生成一次账号私钥,更新私钥需要获取一个新的EAB授权 + encrypt: true pluginType: access type: builtIn scriptFilePath: /plugins/plugin-cert/access/eab-access.js diff --git a/packages/ui/certd-server/metadata/deploy_TencentRefreshCert.yaml b/packages/ui/certd-server/metadata/deploy_TencentRefreshCert.yaml index de17cb4bc..11841fad5 100644 --- a/packages/ui/certd-server/metadata/deploy_TencentRefreshCert.yaml +++ b/packages/ui/certd-server/metadata/deploy_TencentRefreshCert.yaml @@ -8,6 +8,7 @@ desc: 根据证书id一键更新腾讯云证书并自动部署(Id不变), icon: svg:icon-tencentcloud group: tencent needPlus: false +deprecated: 腾讯更新证书(Id不变)接口已失效,本插件已下架,请使用其他接口 input: cert: title: 域名证书 diff --git a/packages/ui/certd-server/metadata/deploy_VolcengineDeployToVKE.yaml b/packages/ui/certd-server/metadata/deploy_VolcengineDeployToVKE.yaml index 37297b8e5..3d5071a64 100644 --- a/packages/ui/certd-server/metadata/deploy_VolcengineDeployToVKE.yaml +++ b/packages/ui/certd-server/metadata/deploy_VolcengineDeployToVKE.yaml @@ -68,12 +68,12 @@ input: component: name: remote-select vModel: value - mode: tags + mode: default type: plugin action: onGetClusterList search: false pager: false - multi: true + multi: false watches: - certDomains - accessId @@ -102,8 +102,6 @@ input: value: Public - label: 私网 value: Private - - label: 集群内 - value: TargetCluster value: Public required: true order: 0 diff --git a/packages/ui/certd-server/package.json b/packages/ui/certd-server/package.json index 4984a4d2e..d889ef9c6 100644 --- a/packages/ui/certd-server/package.json +++ b/packages/ui/certd-server/package.json @@ -1,6 +1,6 @@ { "name": "@certd/ui-server", - "version": "1.39.12", + "version": "1.39.13", "description": "fast-server base midway", "private": true, "type": "module", @@ -53,20 +53,20 @@ "@aws-sdk/client-sts": "^3.990.0", "@azure/arm-dns": "^5.1.0", "@azure/identity": "^4.13.1", - "@certd/acme-client": "^1.39.12", - "@certd/basic": "^1.39.12", - "@certd/commercial-core": "^1.39.12", + "@certd/acme-client": "^1.39.13", + "@certd/basic": "^1.39.13", + "@certd/commercial-core": "^1.39.13", "@certd/cv4pve-api-javascript": "^8.4.2", - "@certd/jdcloud": "^1.39.12", - "@certd/lib-huawei": "^1.39.12", - "@certd/lib-k8s": "^1.39.12", - "@certd/lib-server": "^1.39.12", - "@certd/midway-flyway-js": "^1.39.12", - "@certd/pipeline": "^1.39.12", - "@certd/plugin-cert": "^1.39.12", - "@certd/plugin-lib": "^1.39.12", - "@certd/plugin-plus": "^1.39.12", - "@certd/plus-core": "^1.39.12", + "@certd/jdcloud": "^1.39.13", + "@certd/lib-huawei": "^1.39.13", + "@certd/lib-k8s": "^1.39.13", + "@certd/lib-server": "^1.39.13", + "@certd/midway-flyway-js": "^1.39.13", + "@certd/pipeline": "^1.39.13", + "@certd/plugin-cert": "^1.39.13", + "@certd/plugin-lib": "^1.39.13", + "@certd/plugin-plus": "^1.39.13", + "@certd/plus-core": "^1.39.13", "@google-cloud/dns": "^5.3.1", "@google-cloud/publicca": "^1.3.0", "@huaweicloud/huaweicloud-sdk-cdn": "^3.1.185", @@ -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", diff --git a/packages/ui/certd-server/src/controller/user/mine/mine-controller.ts b/packages/ui/certd-server/src/controller/user/mine/mine-controller.ts index d61438eec..95f7c96da 100644 --- a/packages/ui/certd-server/src/controller/user/mine/mine-controller.ts +++ b/packages/ui/certd-server/src/controller/user/mine/mine-controller.ts @@ -1,9 +1,10 @@ -import { BaseController, Constants } from '@certd/lib-server'; +import { BaseController, Constants, SysSettingsService } from '@certd/lib-server'; import { ALL, Body, Controller, Inject, Post, Provide } from '@midwayjs/core'; import { PasskeyService } from '../../../modules/login/service/passkey-service.js'; import { RoleService } from '../../../modules/sys/authority/service/role-service.js'; import { UserService } from '../../../modules/sys/authority/service/user-service.js'; import { ApiTags } from '@midwayjs/swagger'; +import { CodeService } from '../../../modules/basic/service/code-service.js'; /** */ @@ -20,8 +21,13 @@ export class MineController extends BaseController { @Inject() passkeyService: PasskeyService; + @Inject() + codeService: CodeService; - @Post('/info', { description: Constants.per.authOnly, summary: "查询用户信息" }) + @Inject() + sysSettingsService: SysSettingsService; + + @Post('/info', { description: Constants.per.authOnly, summary: '查询用户信息' }) public async info() { const userId = this.getUserId(); const user = await this.userService.info(userId); @@ -35,21 +41,75 @@ export class MineController extends BaseController { return this.ok(user); } - @Post('/changePassword', { description: Constants.per.authOnly, summary: "修改密码" }) + @Post('/changePassword', { description: Constants.per.authOnly, summary: '修改密码' }) public async changePassword(@Body(ALL) body: any) { const userId = this.getUserId(); await this.userService.changePassword(userId, body); return this.ok({}); } - @Post('/updateProfile', { description: Constants.per.authOnly, summary: "更新用户资料" }) + @Post('/updateProfile', { description: Constants.per.authOnly, summary: '更新用户资料' }) public async updateProfile(@Body(ALL) body: any) { const userId = this.getUserId(); - + await this.userService.updateProfile(userId, { avatar: body.avatar, nickName: body.nickName, }); return this.ok({}); } + + @Post('/contact/capability', { description: Constants.per.authOnly, summary: '查询联系方式绑定能力' }) + public async contactCapability() { + const settings = await this.sysSettingsService.getPrivateSettings(); + return this.ok({ + smsEnabled: !!settings.sms?.config?.accessId, + }); + } + + @Post('/contact/verifyIdentity', { description: Constants.per.authOnly, summary: '验证本人操作' }) + public async verifyContactIdentity(@Body(ALL) body: { identityType: 'password' | 'email' | 'mobile'; identityPassword?: string; identityValidateCode?: string }) { + const userId = this.getUserId(); + await this.userService.verifyIdentity(userId, body, this.codeService); + const validationCode = this.codeService.setValidationValue({ + type: 'contactIdentity', + userId, + identityType: body.identityType, + }); + return this.ok({ validationCode }); + } + + @Post('/contact/mobile', { description: Constants.per.authOnly, summary: '绑定或修改手机号' }) + public async updateMobile(@Body(ALL) body: { phoneCode?: string; mobile: string; validateCode: string; identityValidationCode: string }) { + const userId = this.getUserId(); + this.userService.checkContactIdentityValidation(userId, body.identityValidationCode, this.codeService); + await this.codeService.checkSmsCode({ + mobile: body.mobile, + phoneCode: body.phoneCode || '86', + smsCode: body.validateCode, + verificationType: 'bindMobile', + throwError: true, + }); + await this.userService.updateMobile(userId, { + phoneCode: body.phoneCode, + mobile: body.mobile, + }); + return this.ok({}); + } + + @Post('/contact/email', { description: Constants.per.authOnly, summary: '绑定或修改邮箱' }) + public async updateEmail(@Body(ALL) body: { email: string; validateCode: string; identityValidationCode: string }) { + const userId = this.getUserId(); + this.userService.checkContactIdentityValidation(userId, body.identityValidationCode, this.codeService); + this.codeService.checkEmailCode({ + email: body.email, + validateCode: body.validateCode, + verificationType: 'bindEmail', + throwError: true, + }); + await this.userService.updateEmail(userId, { + email: body.email, + }); + return this.ok({}); + } } diff --git a/packages/ui/certd-server/src/modules/auto/auto-c-register-cron.ts b/packages/ui/certd-server/src/modules/auto/auto-cron.ts similarity index 98% rename from packages/ui/certd-server/src/modules/auto/auto-c-register-cron.ts rename to packages/ui/certd-server/src/modules/auto/auto-cron.ts index f84745a83..879479b7b 100644 --- a/packages/ui/certd-server/src/modules/auto/auto-c-register-cron.ts +++ b/packages/ui/certd-server/src/modules/auto/auto-cron.ts @@ -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); diff --git a/packages/ui/certd-server/src/modules/auto/auto-fix.test.ts b/packages/ui/certd-server/src/modules/auto/auto-fix.test.ts new file mode 100644 index 000000000..101ee5af0 --- /dev/null +++ b/packages/ui/certd-server/src/modules/auto/auto-fix.test.ts @@ -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"), + }); + }); + +}); diff --git a/packages/ui/certd-server/src/modules/auto/auto-fix.ts b/packages/ui/certd-server/src/modules/auto/auto-fix.ts new file mode 100644 index 000000000..50f2975f2 --- /dev/null +++ b/packages/ui/certd-server/src/modules/auto/auto-fix.ts @@ -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); + } +} diff --git a/packages/ui/certd-server/src/modules/auto/auto-a-init-site.ts b/packages/ui/certd-server/src/modules/auto/auto-init-site.ts similarity index 95% rename from packages/ui/certd-server/src/modules/auto/auto-a-init-site.ts rename to packages/ui/certd-server/src/modules/auto/auto-init-site.ts index aedeeb8af..171aee397 100644 --- a/packages/ui/certd-server/src/modules/auto/auto-a-init-site.ts +++ b/packages/ui/certd-server/src/modules/auto/auto-init-site.ts @@ -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(); diff --git a/packages/ui/certd-server/src/modules/auto/auto-b-load-plugins.ts b/packages/ui/certd-server/src/modules/auto/auto-load-plugins.ts similarity index 89% rename from packages/ui/certd-server/src/modules/auto/auto-b-load-plugins.ts rename to packages/ui/certd-server/src/modules/auto/auto-load-plugins.ts index 1dfef42b8..2526c622c 100644 --- a/packages/ui/certd-server/src/modules/auto/auto-b-load-plugins.ts +++ b/packages/ui/certd-server/src/modules/auto/auto-load-plugins.ts @@ -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") { diff --git a/packages/ui/certd-server/src/modules/auto/auto-d-mitter-register.ts b/packages/ui/certd-server/src/modules/auto/auto-mitter-register.ts similarity index 78% rename from packages/ui/certd-server/src/modules/auto/auto-d-mitter-register.ts rename to packages/ui/certd-server/src/modules/auto/auto-mitter-register.ts index 438d1703b..1b87f004a 100644 --- a/packages/ui/certd-server/src/modules/auto/auto-d-mitter-register.ts +++ b/packages/ui/certd-server/src/modules/auto/auto-mitter-register.ts @@ -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(); } diff --git a/packages/ui/certd-server/src/modules/auto/auto-e-pipeline-emitter-register.ts b/packages/ui/certd-server/src/modules/auto/auto-pipeline-emitter-register.ts similarity index 76% rename from packages/ui/certd-server/src/modules/auto/auto-e-pipeline-emitter-register.ts rename to packages/ui/certd-server/src/modules/auto/auto-pipeline-emitter-register.ts index eb420fa69..12605c94a 100644 --- a/packages/ui/certd-server/src/modules/auto/auto-e-pipeline-emitter-register.ts +++ b/packages/ui/certd-server/src/modules/auto/auto-pipeline-emitter-register.ts @@ -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); }); } diff --git a/packages/ui/certd-server/src/modules/auto/auto-z.ts b/packages/ui/certd-server/src/modules/auto/auto-print.ts similarity index 96% rename from packages/ui/certd-server/src/modules/auto/auto-z.ts rename to packages/ui/certd-server/src/modules/auto/auto-print.ts index feefd5acb..79a9cda17 100644 --- a/packages/ui/certd-server/src/modules/auto/auto-z.ts +++ b/packages/ui/certd-server/src/modules/auto/auto-print.ts @@ -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(); diff --git a/packages/ui/certd-server/src/modules/auto/auto-register.ts b/packages/ui/certd-server/src/modules/auto/auto-register.ts new file mode 100644 index 000000000..6a7fbb120 --- /dev/null +++ b/packages/ui/certd-server/src/modules/auto/auto-register.ts @@ -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(); + } +} diff --git a/packages/ui/certd-server/src/modules/cname/entity/cname-provider.ts b/packages/ui/certd-server/src/modules/cname/entity/cname-provider.ts index 4da5fb633..956ef8f16 100644 --- a/packages/ui/certd-server/src/modules/cname/entity/cname-provider.ts +++ b/packages/ui/certd-server/src/modules/cname/entity/cname-provider.ts @@ -11,6 +11,8 @@ export class CnameProviderEntity { userId: number; @Column({ comment: '域名', length: 100 }) domain: string; + @Column({ comment: '子域名托管', length: 100, nullable: true }) + subdomain: string; @Column({ comment: 'DNS提供商类型', name: 'dns_provider_type', length: 20 }) dnsProviderType: string; @Column({ comment: 'DNS授权Id', name: 'access_id' }) diff --git a/packages/ui/certd-server/src/modules/cname/service/cname-provider-service.ts b/packages/ui/certd-server/src/modules/cname/service/cname-provider-service.ts index 26cc12311..ca452079a 100644 --- a/packages/ui/certd-server/src/modules/cname/service/cname-provider-service.ts +++ b/packages/ui/certd-server/src/modules/cname/service/cname-provider-service.ts @@ -98,6 +98,18 @@ export class CnameProviderService extends BaseService { return null; } + async getSubDomains() { + const list = await this.repository.find({ + select: { + subdomain: true, + }, + where: { + disabled: false, + }, + }); + return list.map(item => item.subdomain?.trim()).filter((item): item is string => !!item); + } + async list(req: ListReq): Promise { const list = await super.list(req); const sysPrivateSettings = await this.settingsService.getSetting(SysPrivateSettings); diff --git a/packages/ui/certd-server/src/modules/pipeline/service/getter/sub-domain-getter.test.ts b/packages/ui/certd-server/src/modules/pipeline/service/getter/sub-domain-getter.test.ts new file mode 100644 index 000000000..a1b75336a --- /dev/null +++ b/packages/ui/certd-server/src/modules/pipeline/service/getter/sub-domain-getter.test.ts @@ -0,0 +1,28 @@ +import assert from "node:assert/strict"; +import { describe, it } from "mocha"; +import { SubDomainsGetter } from "./sub-domain-getter.js"; + +describe("SubDomainsGetter", () => { + it("returns subdomains configured on system cname providers", async () => { + const subDomainService = { + async getListByUserId() { + return ["example.com"]; + }, + } as any; + const domainService = { + async findOne() { + return null; + }, + } as any; + const cnameProviderService = { + async getSubDomains() { + return ["cname-hosted.example.com"]; + }, + } as any; + + const getter = new SubDomainsGetter(1, 2, subDomainService, domainService, cnameProviderService); + + assert.deepEqual(await getter.getSubDomains(), ["cname-hosted.example.com", "example.com"]); + assert.equal(await getter.hasSubDomain("txt.certd.cname-hosted.example.com"), "cname-hosted.example.com"); + }); +}); diff --git a/packages/ui/certd-server/src/modules/pipeline/service/getter/sub-domain-getter.ts b/packages/ui/certd-server/src/modules/pipeline/service/getter/sub-domain-getter.ts index 399a23a9d..f5b0f490a 100644 --- a/packages/ui/certd-server/src/modules/pipeline/service/getter/sub-domain-getter.ts +++ b/packages/ui/certd-server/src/modules/pipeline/service/getter/sub-domain-getter.ts @@ -1,22 +1,30 @@ import { ISubDomainsGetter } from "@certd/plugin-cert"; import { SubDomainService } from "../sub-domain-service.js"; import { DomainService } from "../../../cert/service/domain-service.js"; +import { CnameProviderService } from "../../../cname/service/cname-provider-service.js"; export class SubDomainsGetter implements ISubDomainsGetter { userId: number; projectId: number; subDomainService: SubDomainService; domainService: DomainService; + cnameProviderService: CnameProviderService; - constructor(userId: number, projectId: number, subDomainService: SubDomainService, domainService: DomainService) { + constructor(userId: number, projectId: number, subDomainService: SubDomainService, domainService: DomainService, cnameProviderService: CnameProviderService) { this.userId = userId; this.projectId = projectId; this.subDomainService = subDomainService; this.domainService = domainService; + this.cnameProviderService = cnameProviderService; } async getSubDomains() { - return await this.subDomainService.getListByUserId(this.userId, this.projectId) + const projectSubDomains = await this.subDomainService.getListByUserId(this.userId, this.projectId) || []; + const cnameProviderSubDomains = await this.cnameProviderService.getSubDomains(); + return [...projectSubDomains, ...cnameProviderSubDomains] + .map(item => item?.trim()) + .filter((item): item is string => !!item) + .sort((a, b) => b.length - a.length); } async hasSubDomain(fullDomain: string) { diff --git a/packages/ui/certd-server/src/modules/pipeline/service/getter/task-service-getter.ts b/packages/ui/certd-server/src/modules/pipeline/service/getter/task-service-getter.ts index 682c4dd2a..e42b8f116 100644 --- a/packages/ui/certd-server/src/modules/pipeline/service/getter/task-service-getter.ts +++ b/packages/ui/certd-server/src/modules/pipeline/service/getter/task-service-getter.ts @@ -12,6 +12,7 @@ import { SubDomainService } from "../sub-domain-service.js"; import { CertInfoGetter } from "./cert-info-getter.js"; import { CertInfoService } from "../../../monitor/index.js"; import { ICertInfoGetter } from "@certd/plugin-lib"; +import { CnameProviderService } from "../../../cname/service/cname-provider-service.js"; const serviceNames = [ 'ocrService', @@ -53,7 +54,8 @@ export class TaskServiceGetter implements IServiceGetter{ async getSubDomainsGetter(): Promise { const subDomainsService:SubDomainService = await this.appCtx.getAsync("subDomainService") const domainService:DomainService = await this.appCtx.getAsync("domainService") - return new SubDomainsGetter(this.userId,this.projectId, subDomainsService,domainService) + const cnameProviderService:CnameProviderService = await this.appCtx.getAsync("cnameProviderService") + return new SubDomainsGetter(this.userId,this.projectId, subDomainsService,domainService,cnameProviderService) } async getCertInfoGetter(): Promise { @@ -102,4 +104,3 @@ export type TaskServiceCreateReq = { - diff --git a/packages/ui/certd-server/src/modules/sys/authority/service/user-contact.test.ts b/packages/ui/certd-server/src/modules/sys/authority/service/user-contact.test.ts new file mode 100644 index 000000000..ce0f6818a --- /dev/null +++ b/packages/ui/certd-server/src/modules/sys/authority/service/user-contact.test.ts @@ -0,0 +1,27 @@ +/// + +import assert from 'node:assert/strict'; +import { Not } from 'typeorm'; +import { buildUserContactConflictWhere } from './user-service.js'; + +describe('buildUserContactConflictWhere', () => { + it('checks username, mobile and email conflicts except current user', () => { + const where = buildUserContactConflictWhere('user@example.com', 12); + + assert.deepEqual(where, [ + { username: 'user@example.com', id: Not(12) }, + { mobile: 'user@example.com', id: Not(12) }, + { email: 'user@example.com', id: Not(12) }, + ]); + }); + + it('trims contact value before building conflict query', () => { + const where = buildUserContactConflictWhere(' 13800138000 ', 3); + + assert.deepEqual(where, [ + { username: '13800138000', id: Not(3) }, + { mobile: '13800138000', id: Not(3) }, + { email: '13800138000', id: Not(3) }, + ]); + }); +}); diff --git a/packages/ui/certd-server/src/modules/sys/authority/service/user-service.ts b/packages/ui/certd-server/src/modules/sys/authority/service/user-service.ts index 9015040d9..750f2d769 100644 --- a/packages/ui/certd-server/src/modules/sys/authority/service/user-service.ts +++ b/packages/ui/certd-server/src/modules/sys/authority/service/user-service.ts @@ -1,6 +1,6 @@ import { Inject, Provide, Scope, ScopeEnum } from '@midwayjs/core'; import { InjectEntityModel } from '@midwayjs/typeorm'; -import {EntityManager, In, MoreThan, Not, Repository} from 'typeorm'; +import { EntityManager, In, MoreThan, Not, Repository } from 'typeorm'; import { UserEntity } from '../entity/user.js'; import * as _ from 'lodash-es'; import { BaseService, CommonException, Constants, FileService, SysInstallInfo, SysSettingsService } from '@certd/lib-server'; @@ -18,15 +18,22 @@ import { OauthBoundService } from '../../../login/service/oauth-bound-service.js export type RegisterType = 'username' | 'mobile' | 'email'; export type ForgotPasswordType = 'mobile' | 'email'; -export const AdminRoleId = 1 +export const AdminRoleId = 1; + +export function buildUserContactConflictWhere(value: string, userId: number) { + const contact = value?.trim(); + return [ + { username: contact, id: Not(userId) }, + { mobile: contact, id: Not(userId) }, + { email: contact, id: Not(userId) }, + ]; +} /** * 系统用户 */ @Provide() @Scope(ScopeEnum.Request, { allowDowngrade: true }) export class UserService extends BaseService { - - @InjectEntityModel(UserEntity) repository: Repository; @Inject() @@ -44,10 +51,9 @@ export class UserService extends BaseService { @Inject() dbAdapter: DbAdapter; - @Inject() + @Inject() oauthBoundService: OauthBoundService; - //@ts-ignore getRepository() { return this.repository; @@ -145,7 +151,7 @@ export class UserService extends BaseService { return bcrypt.hashSync(plainPassword, salt); } - async findOne(param: Record) { + async findOne(param: Record) { return this.repository.findOne({ where: param, }); @@ -177,12 +183,11 @@ export class UserService extends BaseService { return await this.roleService.getPermissionByRoleIds(roleIds); } - async register(type: string, user: UserEntity,withTx?:(tx: EntityManager)=>Promise) { + async register(type: string, user: UserEntity, withTx?: (tx: EntityManager) => Promise) { if (!user.password) { user.password = simpleNanoId(); } - if (user.username) { const username = user.username; const old = await this.findOne([{ username: username }, { mobile: username }, { email: username }]); @@ -208,7 +213,6 @@ export class UserService extends BaseService { } } - if (!user.username) { user.username = 'user_' + simpleNanoId(); } @@ -235,7 +239,7 @@ export class UserService extends BaseService { const userRole: UserRoleEntity = UserRoleEntity.of(newUser.id, Constants.role.defaultUser); await txManager.save(userRole); - if(withTx) { + if (withTx) { await withTx(txManager); } }); @@ -247,35 +251,26 @@ export class UserService extends BaseService { return newUser; } - async forgotPassword( - data: { - type: ForgotPasswordType; - input?: string, - phoneCode?: string, - validateCode: string, - password: string, - confirmPassword: string, - } - ) { - if(!data.type) { + async forgotPassword(data: { type: ForgotPasswordType; input?: string; phoneCode?: string; validateCode: string; password: string; confirmPassword: string }) { + if (!data.type) { throw new CommonException('找回类型不能为空'); } - if(data.password !== data.confirmPassword) { + if (data.password !== data.confirmPassword) { throw new CommonException('两次输入的密码不一致'); } - const where :any= { + const where: any = { [data.type]: data.input, }; - if (data.type === 'mobile' ) { - where.phoneCode = data.phoneCode ?? '86'; + if (data.type === 'mobile') { + where.phoneCode = data.phoneCode ?? '86'; } const user = await this.findOne({ [data.type]: data.input }); - console.log('user', user) - if(!user) { + console.log('user', user); + if (!user) { throw new CommonException('用户不存在'); // return; } - await this.resetPassword(user.id, data.password) + await this.resetPassword(user.id, data.password); return user.username; } @@ -376,30 +371,102 @@ export class UserService extends BaseService { } async getAdmins() { - const admins = await this.userRoleService.find({ - where: { - roleId: AdminRoleId, - }, - }); + const admins = await this.userRoleService.find({ + where: { + roleId: AdminRoleId, + }, + }); - const userIds = admins.map(item => item.userId); - return await this.repository.find({ - where: { - id: In(userIds), - status: 1, - }, - order: { - updateTime: 'DESC', - }, - }) + const userIds = admins.map(item => item.userId); + return await this.repository.find({ + where: { + id: In(userIds), + status: 1, + }, + order: { + updateTime: 'DESC', + }, + }); } async updateProfile(userId: any, body: any) { - await this.update({ id: userId, ...body, - }) + }); + } + + async verifyIdentity(userId: number, body: { identityType: 'password' | 'email' | 'mobile'; identityPassword?: string; identityValidateCode?: string }, codeService: any) { + const user = await this.info(userId); + if (body.identityType === 'password') { + const passwordChecked = await this.checkPassword(body.identityPassword, user.password, user.passwordVersion); + if (!passwordChecked) { + throw new CommonException('密码错误'); + } + return; + } + if (body.identityType === 'email') { + if (!user.email) { + throw new CommonException('当前账号未绑定邮箱'); + } + codeService.checkEmailCode({ + email: user.email, + validateCode: body.identityValidateCode, + verificationType: 'contactIdentity', + throwError: true, + }); + return; + } + if (body.identityType === 'mobile') { + if (!user.mobile) { + throw new CommonException('当前账号未绑定手机号'); + } + await codeService.checkSmsCode({ + mobile: user.mobile, + phoneCode: user.phoneCode || '86', + smsCode: body.identityValidateCode, + verificationType: 'contactIdentity', + throwError: true, + }); + return; + } + throw new CommonException('不支持的验证方式'); + } + + checkContactIdentityValidation(userId: number, validationCode: string, codeService: any) { + const validationValue = codeService.getValidationValue(validationCode); + if (!validationValue || validationValue.type !== 'contactIdentity' || validationValue.userId !== userId) { + throw new CommonException('请先验证本人操作'); + } + } + + async updateMobile(userId: number, body: { phoneCode?: string; mobile: string }) { + const mobile = body.mobile?.trim(); + if (!mobile) { + throw new CommonException('手机号不能为空'); + } + const old = await this.findOne(buildUserContactConflictWhere(mobile, userId)); + if (old != null) { + throw new CommonException('手机号已被占用'); + } + await this.repository.update(userId, { + phoneCode: body.phoneCode || '86', + mobile, + }); + } + + async updateEmail(userId: number, body: { email: string }) { + const email = body.email?.trim(); + if (!email) { + throw new CommonException('邮箱不能为空'); + } + const old = await this.findOne(buildUserContactConflictWhere(email, userId)); + if (old != null) { + throw new CommonException('邮箱已被占用'); + } + await this.repository.update(userId, { + email, + }); } async getAllUserIds() { @@ -408,7 +475,7 @@ export class UserService extends BaseService { where: { status: 1, }, - }) + }); return users.map(item => item.id); } } diff --git a/packages/ui/certd-server/src/plugins/plugin-aliyun/plugin/deploy-to-esa/index.ts b/packages/ui/certd-server/src/plugins/plugin-aliyun/plugin/deploy-to-esa/index.ts index 1c54b8563..c5e657825 100644 --- a/packages/ui/certd-server/src/plugins/plugin-aliyun/plugin/deploy-to-esa/index.ts +++ b/packages/ui/certd-server/src/plugins/plugin-aliyun/plugin/deploy-to-esa/index.ts @@ -275,11 +275,13 @@ export class AliyunDeployCertToESA extends AbstractTaskPlugin { } }); - const list = certListRes.Result; + let list = certListRes.Result || []; + list = list.filter((item: any) => item.Type === "cas"); if (!list || list.length === 0) { - this.logger.info(`站点[${siteId}]没有证书, 无需删除`); + this.logger.info(`站点[${siteId}]没有CAS证书, 无需删除`); return } + if (list.length < certLimit) { this.logger.info(`站点[${siteId}]证书数量(${list.length})未超限制, 无需删除`); return; diff --git a/packages/ui/certd-server/src/plugins/plugin-cert/access/eab-access.test.ts b/packages/ui/certd-server/src/plugins/plugin-cert/access/eab-access.test.ts new file mode 100644 index 000000000..1bad9ed63 --- /dev/null +++ b/packages/ui/certd-server/src/plugins/plugin-cert/access/eab-access.test.ts @@ -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/); + }); +}); diff --git a/packages/ui/certd-server/src/plugins/plugin-cert/access/eab-access.ts b/packages/ui/certd-server/src/plugins/plugin-cert/access/eab-access.ts index 1162c4df9..8d619d460 100644 --- a/packages/ui/certd-server/src/plugins/plugin-cert/access/eab-access.ts +++ b/packages/ui/certd-server/src/plugins/plugin-cert/access/eab-access.ts @@ -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", 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,请点击生成重新生成账号私钥\n注意:google的EAB只能生成一次账号私钥,更新私钥需要获取一个新的EAB授权", + 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(); diff --git a/packages/ui/certd-server/src/plugins/plugin-cert/plugin/cert-plugin/acme.test.ts b/packages/ui/certd-server/src/plugins/plugin-cert/plugin/cert-plugin/acme.test.ts new file mode 100644 index 000000000..84c1c9fc5 --- /dev/null +++ b/packages/ui/certd-server/src/plugins/plugin-cert/plugin/cert-plugin/acme.test.ts @@ -0,0 +1,176 @@ +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账号私钥后重试/); + }); +}); + +describe("AcmeService challenge", () => { + it("parses cname TXT full record to choose the delegated DNS zone", async () => { + const parseCalls: string[] = []; + const service = new AcmeService({ + userId: 1, + userContext: {} as any, + logger: logger as any, + sslProvider: "letsencrypt", + domainParser: { + async parse(fullDomain: string) { + parseCalls.push(fullDomain); + if (fullDomain === "certd-key.cname.sub.example.com") { + return "sub.example.com"; + } + return "example.com"; + }, + } as any, + }); + const dnsProvider = { + usePunyCode() { + return false; + }, + async createRecord(recordReq: any) { + assert.equal(recordReq.domain, "sub.example.com"); + assert.equal(recordReq.fullRecord, "certd-key.cname.sub.example.com"); + assert.equal(recordReq.hostRecord, "certd-key.cname"); + return { id: "record-id" }; + }, + } as any; + + await service.challengeCreateFn( + { + identifier: { + value: "www.example.com", + }, + challenges: [ + { + type: "dns-01", + }, + ], + }, + async () => "key-auth", + { + domainsVerifyPlan: { + "www.example.com": { + type: "cname", + domain: "www.example.com", + mainDomain: "example.com", + cnameVerifyPlan: { + domain: "cname.sub.example.com", + fullRecord: "certd-key.cname.sub.example.com", + dnsProvider, + }, + }, + }, + } + ); + + assert.deepEqual(parseCalls, ["www.example.com", "certd-key.cname.sub.example.com"]); + }); +}); diff --git a/packages/ui/certd-server/src/plugins/plugin-cert/plugin/cert-plugin/acme.ts b/packages/ui/certd-server/src/plugins/plugin-cert/plugin/cert-plugin/acme.ts index 9661e2b25..eb1f52af9 100644 --- a/packages/ui/certd-server/src/plugins/plugin-cert/plugin/cert-plugin/acme.ts +++ b/packages/ui/certd-server/src/plugins/plugin-cert/plugin/cert-plugin/acme.ts @@ -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 { - 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 { - 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); } @@ -264,8 +324,8 @@ export class AcmeService { const cname: CnameVerifyPlan = domainVerifyPlan.cnameVerifyPlan; if (cname) { dnsProvider = cname.dnsProvider; - domain = await this.options.domainParser.parse(cname.domain); fullRecord = cname.fullRecord; + domain = await this.options.domainParser.parse(fullRecord); } else { this.logger.error(`未找到域名${fullDomain}的CNAME校验计划,请修改证书申请配置`); } diff --git a/packages/ui/certd-server/src/plugins/plugin-tencent/plugin/refresh-cert/index.ts b/packages/ui/certd-server/src/plugins/plugin-tencent/plugin/refresh-cert/index.ts index 5c62ead93..6c55532e2 100644 --- a/packages/ui/certd-server/src/plugins/plugin-tencent/plugin/refresh-cert/index.ts +++ b/packages/ui/certd-server/src/plugins/plugin-tencent/plugin/refresh-cert/index.ts @@ -21,6 +21,7 @@ import { omit } from "lodash-es"; //插件分组 group: pluginGroups.tencent.key, needPlus: false, + deprecated: "腾讯更新证书(Id不变)接口已失效,本插件已下架,请使用其他接口", default: { //默认值配置照抄即可 strategy: { diff --git a/packages/ui/certd-server/tsconfig.json b/packages/ui/certd-server/tsconfig.json index d09119a81..7cda6a102 100644 --- a/packages/ui/certd-server/tsconfig.json +++ b/packages/ui/certd-server/tsconfig.json @@ -23,3 +23,4 @@ }, "exclude": ["*.js", "*.ts", "dist", "node_modules", "src/**/*.test.ts", "test"] } + diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index d508a1527..ab38b87cb 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -36,6 +36,9 @@ importers: axios: specifier: ^1.9.0 version: 1.9.0(debug@4.4.3) + cross-env: + specifier: ^7.0.3 + version: 7.0.3 medium-zoom: specifier: ^1.1.0 version: 1.1.0 @@ -49,7 +52,7 @@ importers: packages/core/acme-client: dependencies: '@certd/basic': - specifier: ^1.39.12 + specifier: ^1.39.13 version: link:../basic '@peculiar/x509': specifier: ^1.11.0 @@ -94,6 +97,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 +112,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 +197,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 +209,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 @@ -219,11 +234,11 @@ importers: packages/core/pipeline: dependencies: '@certd/basic': - specifier: ^1.39.12 + specifier: ^1.39.13 version: link:../basic '@certd/plus-core': specifier: ^1.39.12 - version: 1.39.12 + version: link:../../pro/plus-core dayjs: specifier: ^1.11.7 version: 1.11.13 @@ -267,6 +282,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 +294,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 +334,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 +362,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 +374,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 +436,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 @@ -421,7 +457,7 @@ importers: packages/libs/lib-k8s: dependencies: '@certd/basic': - specifier: ^1.39.12 + specifier: ^1.39.13 version: link:../../core/basic '@kubernetes/client-node': specifier: 0.21.0 @@ -436,6 +472,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 +484,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 @@ -461,20 +503,20 @@ importers: packages/libs/lib-server: dependencies: '@certd/acme-client': - specifier: ^1.39.12 + specifier: ^1.39.13 version: link:../../core/acme-client '@certd/basic': - specifier: ^1.39.12 + specifier: ^1.39.13 version: link:../../core/basic '@certd/pipeline': - specifier: ^1.39.12 + specifier: ^1.39.13 version: link:../../core/pipeline '@certd/plugin-lib': - specifier: ^1.39.12 + specifier: ^1.39.13 version: link:../../plugins/plugin-lib '@certd/plus-core': specifier: ^1.39.12 - version: 1.39.12 + version: link:../../pro/plus-core '@midwayjs/cache': specifier: 3.14.0 version: 3.14.0 @@ -505,9 +547,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 +578,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 +590,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 +639,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 +657,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 @@ -628,16 +679,16 @@ importers: packages/plugins/plugin-cert: dependencies: '@certd/acme-client': - specifier: ^1.39.12 + specifier: ^1.39.13 version: link:../../core/acme-client '@certd/basic': - specifier: ^1.39.12 + specifier: ^1.39.13 version: link:../../core/basic '@certd/pipeline': - specifier: ^1.39.12 + specifier: ^1.39.13 version: link:../../core/pipeline '@certd/plugin-lib': - specifier: ^1.39.12 + specifier: ^1.39.13 version: link:../plugin-lib psl: specifier: ^1.9.0 @@ -661,6 +712,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 +724,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 @@ -701,17 +758,17 @@ importers: specifier: ^3.964.0 version: 3.964.0(aws-crt@1.26.2) '@certd/acme-client': - specifier: ^1.39.12 + specifier: ^1.39.13 version: link:../../core/acme-client '@certd/basic': - specifier: ^1.39.12 + specifier: ^1.39.13 version: link:../../core/basic '@certd/pipeline': - specifier: ^1.39.12 + specifier: ^1.39.13 version: link:../../core/pipeline '@certd/plus-core': specifier: ^1.39.12 - version: 1.39.12 + version: link:../../pro/plus-core '@kubernetes/client-node': specifier: 0.21.0 version: 0.21.0 @@ -776,6 +833,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 +845,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 @@ -804,16 +867,16 @@ importers: packages/pro/commercial-core: dependencies: '@certd/basic': - specifier: ^1.39.7 + specifier: ^1.39.12 version: link:../../core/basic '@certd/lib-server': - specifier: ^1.39.7 + specifier: ^1.39.12 version: link:../../libs/lib-server '@certd/pipeline': - specifier: ^1.39.7 + specifier: ^1.39.12 version: link:../../core/pipeline '@certd/plus-core': - specifier: ^1.39.7 + specifier: ^1.39.12 version: link:../plus-core '@midwayjs/core': specifier: 3.20.11 @@ -858,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 @@ -867,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) + mocha: + specifier: ^10.2.0 + version: 10.8.2 prettier: specifier: ^2.8.8 version: 2.8.8 @@ -889,16 +958,16 @@ importers: packages/pro/plugin-plus: dependencies: '@certd/basic': - specifier: ^1.39.7 + specifier: ^1.39.12 version: link:../../core/basic '@certd/pipeline': - specifier: ^1.39.7 + specifier: ^1.39.12 version: link:../../core/pipeline '@certd/plugin-lib': - specifier: ^1.39.7 + specifier: ^1.39.12 version: link:../../plugins/plugin-lib '@certd/plus-core': - specifier: ^1.39.7 + specifier: ^1.39.12 version: link:../plus-core crypto-js: specifier: ^4.2.0 @@ -943,6 +1012,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 @@ -952,6 +1024,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 @@ -974,7 +1049,7 @@ importers: packages/pro/plus-core: dependencies: '@certd/basic': - specifier: ^1.39.7 + specifier: ^1.39.12 version: link:../../core/basic dayjs: specifier: ^1.11.7 @@ -1007,6 +1082,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 @@ -1016,6 +1094,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 @@ -1139,9 +1220,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) @@ -1270,10 +1348,10 @@ importers: version: 0.1.3(zod@3.24.4) devDependencies: '@certd/lib-iframe': - specifier: ^1.39.12 + specifier: ^1.39.13 version: link:../../libs/lib-iframe '@certd/pipeline': - specifier: ^1.39.12 + specifier: ^1.39.13 version: link:../../core/pipeline '@rollup/plugin-commonjs': specifier: ^25.0.7 @@ -1329,6 +1407,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 @@ -1356,6 +1437,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 @@ -1474,47 +1558,47 @@ importers: specifier: ^4.13.1 version: 4.13.1 '@certd/acme-client': - specifier: ^1.39.12 + specifier: ^1.39.13 version: link:../../core/acme-client '@certd/basic': - specifier: ^1.39.12 + specifier: ^1.39.13 version: link:../../core/basic '@certd/commercial-core': specifier: ^1.39.12 - version: 1.39.12(better-sqlite3@11.10.0)(mysql2@3.14.1)(pg@8.16.0)(reflect-metadata@0.2.2)(ts-node@10.9.2(@types/node@18.19.100)(typescript@5.9.3)) + version: link:../../pro/commercial-core '@certd/cv4pve-api-javascript': specifier: ^8.4.2 version: 8.4.2 '@certd/jdcloud': - specifier: ^1.39.12 + specifier: ^1.39.13 version: link:../../libs/lib-jdcloud '@certd/lib-huawei': - specifier: ^1.39.12 + specifier: ^1.39.13 version: link:../../libs/lib-huawei '@certd/lib-k8s': - specifier: ^1.39.12 + specifier: ^1.39.13 version: link:../../libs/lib-k8s '@certd/lib-server': - specifier: ^1.39.12 + specifier: ^1.39.13 version: link:../../libs/lib-server '@certd/midway-flyway-js': - specifier: ^1.39.12 + specifier: ^1.39.13 version: link:../../libs/midway-flyway-js '@certd/pipeline': - specifier: ^1.39.12 + specifier: ^1.39.13 version: link:../../core/pipeline '@certd/plugin-cert': - specifier: ^1.39.12 + specifier: ^1.39.13 version: link:../../plugins/plugin-cert '@certd/plugin-lib': - specifier: ^1.39.12 + specifier: ^1.39.13 version: link:../../plugins/plugin-lib '@certd/plugin-plus': specifier: ^1.39.12 - version: 1.39.12 + version: link:../../pro/plugin-plus '@certd/plus-core': specifier: ^1.39.12 - version: 1.39.12 + version: link:../../pro/plus-core '@google-cloud/dns': specifier: ^5.3.1 version: 5.3.1 @@ -1617,9 +1701,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 @@ -1786,6 +1867,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 @@ -6801,20 +6888,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'} @@ -7594,6 +7672,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'} @@ -9816,9 +9898,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==} @@ -10228,10 +10307,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'} @@ -11469,18 +11544,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'} @@ -20464,22 +20531,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 @@ -21485,6 +21540,8 @@ snapshots: transitivePeerDependencies: - supports-color + esmock@2.7.5: {} + esniff@2.0.1: dependencies: d: 1.0.2 @@ -23908,8 +23965,6 @@ snapshots: next-tick@1.1.0: {} - nice-try@1.0.5: {} - no-case@3.0.4: dependencies: lower-case: 2.0.2 @@ -24377,8 +24432,6 @@ snapshots: path-is-absolute@1.0.1: {} - path-key@2.0.1: {} - path-key@3.1.1: {} path-key@4.0.0: {} @@ -25728,16 +25781,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: diff --git a/trigger/build.trigger b/trigger/build.trigger index 19f41712f..dfebf9a4a 100644 --- a/trigger/build.trigger +++ b/trigger/build.trigger @@ -1 +1 @@ -23:11 +00:27