mirror of
https://github.com/certd/certd.git
synced 2026-05-15 04:27:31 +08:00
Compare commits
56 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| d131ea3790 | |||
| b849d34be5 | |||
| 58fc9a551c | |||
| 5801f34b3a | |||
| 17cf16ca92 | |||
| 7015b1b232 | |||
| 3b72ca09c6 | |||
| a815d0245b | |||
| 229f22d5a9 | |||
| 22f5cfcfd8 | |||
| 90ba55c043 | |||
| 9f878a353c | |||
| af7297d671 | |||
| 2f172b56e9 | |||
| 9076c8b20e | |||
| 639756dfcd | |||
| 7aa0c7e491 | |||
| 45dedf5bc7 | |||
| 4681ec9008 | |||
| b91826c6e6 | |||
| 686856d0ae | |||
| 9b09d2578d | |||
| f8f51adf88 | |||
| f8ce639717 | |||
| 1c6dc169ac | |||
| 3e5366c74e | |||
| b49ddbfef9 | |||
| b92fd73f53 | |||
| 41b8f51a6a | |||
| aad9045de5 | |||
| fdd5848df4 | |||
| 118c15d046 | |||
| bae4f8e320 | |||
| e0189a566e | |||
| 1cd8d73cdb | |||
| d6e9e5987b | |||
| 8c5aa37745 | |||
| a18a871ac3 | |||
| 90cbff9cf9 | |||
| bae5a04dcc | |||
| 7146570392 | |||
| ae88f85d8e | |||
| a362860137 | |||
| c966896522 | |||
| 5f88da1985 | |||
| 043b80a298 | |||
| ed0da28896 | |||
| 61a0d69d58 | |||
| 3833a9216e | |||
| e59566b5e2 | |||
| e4be0ce464 | |||
| 022dbf0cab | |||
| 1e6b559b89 | |||
| 74bae2005d | |||
| 1731a35d94 | |||
| d8f132919d |
@@ -105,7 +105,7 @@ Certd 是一个支持私有化部署的 SSL/TLS 证书自动化管理平台。
|
|||||||
|
|
||||||
- 前端 `pnpm dev`:启动 Vite 开发服务
|
- 前端 `pnpm dev`:启动 Vite 开发服务
|
||||||
- 前端 `pnpm build`:生产构建
|
- 前端 `pnpm build`:生产构建
|
||||||
- 前端 `pnpm tsc`:类型检查
|
- 不要运行前端 `pnpm tsc` / `vue-tsc`:当前依赖组合中 `vue-tsc@1.8.27` 会直接抛内部错误 `Search string not found: "/supportedTSExtensions = .*(?=;)/"`,不是有效的项目类型检查结果。
|
||||||
- 前端暂不跑单元测试;当前 `test:unit` 只是占位脚本
|
- 前端暂不跑单元测试;当前 `test:unit` 只是占位脚本
|
||||||
|
|
||||||
## 流水线与插件模型
|
## 流水线与插件模型
|
||||||
@@ -216,5 +216,5 @@ Get-ChildItem packages\ui\certd-client\src\views\certd
|
|||||||
- 后端纯单元测试用例放在 `src` 目录内,并尽量与被测文件相邻,例如 `src/utils/random.test.ts`;对应 `test:unit` 只跑 `src/**/*.test.ts`,构建/打包配置应排除这些 `*.test.ts` 文件。
|
- 后端纯单元测试用例放在 `src` 目录内,并尽量与被测文件相邻,例如 `src/utils/random.test.ts`;对应 `test:unit` 只跑 `src/**/*.test.ts`,构建/打包配置应排除这些 `*.test.ts` 文件。
|
||||||
- 单元测试需要 mock ESM 静态 import 时,优先使用 `esmock`,不要为了测试把业务代码改成构造函数注入或把逻辑挪到调用方;各包 `test:unit` 脚本应显式设置 `NODE_ENV=unittest`。
|
- 单元测试需要 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` 暂时不跑单元测试。
|
- 单个 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 <files>`,ESLint 可用命令为 `packages\ui\certd-client\node_modules\.bin\eslint.cmd --fix <files>`;不要为了格式化无关文件而扩大 diff。项目保留了 `tslint` 依赖,但当前主要使用 ESLint + Prettier。
|
- 前端 TS/Vue/locale 等文件改动后,优先只对本次改动文件运行项目现有自动格式化/修复;Windows/PowerShell 下 Prettier 已验证可用命令为 `packages\ui\certd-client\node_modules\.bin\prettier.cmd --write <files>`,ESLint 可用命令为 `packages\ui\certd-client\node_modules\.bin\eslint.cmd --fix <files>`;不要运行 `vue-tsc` / `pnpm tsc`;不要为了格式化无关文件而扩大 diff。项目保留了 `tslint` 依赖,但当前主要使用 ESLint + Prettier。
|
||||||
- 优先对改动包运行聚焦的测试或类型检查;只有跨包影响明显时再考虑全 monorepo 构建。
|
- 优先对改动包运行聚焦的测试;后端可按包运行单元测试,前端优先使用 Prettier/ESLint 做改动文件验证。只有跨包影响明显时再考虑全 monorepo 构建。
|
||||||
|
|||||||
@@ -3,6 +3,49 @@
|
|||||||
All notable changes to this project will be documented in this file.
|
All notable changes to this project will be documented in this file.
|
||||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||||
|
|
||||||
|
# [1.40.0](https://github.com/certd/certd/compare/v1.39.16...v1.40.0) (2026-05-14)
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* 修复第三方登录丢失state时无法在用户信息页面绑定第三方账号的bug ([45dedf5](https://github.com/certd/certd/commit/45dedf5bc779fea852e1f33dda4f31db2765633c))
|
||||||
|
* 修复群晖授权没有显示设备id输入框的bug ([2f172b5](https://github.com/certd/certd/commit/2f172b56e9411303ca15138d827bdb9bafdae4d1))
|
||||||
|
* 修复自动注册后没有跳转到控制台的bug ([4681ec9](https://github.com/certd/certd/commit/4681ec90088a3eb665427b2ac4047ec5ccefd7b3))
|
||||||
|
* 修复clogin登录丢失state问题 ([22f5cfc](https://github.com/certd/certd/commit/22f5cfcfd8462ca74128329eefb3f48b3ee0b7ea))
|
||||||
|
* 修复clogin多选类型登录失败的bug ([9f878a3](https://github.com/certd/certd/commit/9f878a353cd49b7b10bb0a95610ad236bc920dd2))
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* 彩虹登录支持选择多种登录方式 ([7aa0c7e](https://github.com/certd/certd/commit/7aa0c7e491fe660abb62e68792ff5474f19bd5b8))
|
||||||
|
|
||||||
|
### Performance Improvements
|
||||||
|
|
||||||
|
* 第三方登录自动注册的用户支持设置初始化密码 ([a815d02](https://github.com/certd/certd/commit/a815d0245b97efbb948b33d6fc9d49862ce06889))
|
||||||
|
* 头像增加缓存时间 ([7015b1b](https://github.com/certd/certd/commit/7015b1b232602e5168a3eb8bee6d7f1776ae1e74))
|
||||||
|
|
||||||
|
## [1.39.16](https://github.com/certd/certd/compare/v1.39.15...v1.39.16) (2026-05-13)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package root
|
||||||
|
|
||||||
|
## [1.39.15](https://github.com/certd/certd/compare/v1.39.14...v1.39.15) (2026-05-13)
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* 修复第三方登录彩虹登录不上的bug ([bae4f8e](https://github.com/certd/certd/commit/bae4f8e3209d9f9869ecbd7c01655383bac2fe21))
|
||||||
|
|
||||||
|
### Performance Improvements
|
||||||
|
|
||||||
|
* 优化申请时报错日志增加对应域名打印 ([d6e9e59](https://github.com/certd/certd/commit/d6e9e5987bd52ea12ee18745615486eadd4c87ff))
|
||||||
|
* icon选择器增加一套logo集 ([fdd5848](https://github.com/certd/certd/commit/fdd5848df4055a6ee07dc5eabaaf6b718672882d))
|
||||||
|
* **monitor/site:** 新增站点监控页面禁用启用、检查状态两个筛选条件 ([118c15d](https://github.com/certd/certd/commit/118c15d04633a6ef06f2d9e7a7849d20f596e02c))
|
||||||
|
* **network:** 新增全局公共http请求 headers设置 ([aad9045](https://github.com/certd/certd/commit/aad9045de55e76cb2ad09cac74a7bd60a4b47124))
|
||||||
|
|
||||||
|
## [1.39.14](https://github.com/certd/certd/compare/v1.39.13...v1.39.14) (2026-05-11)
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* 修复阿里云订阅流水线创建对话框无法获取阿里订单列表的bug ([a362860](https://github.com/certd/certd/commit/a362860137bfb7072893c844fe775edc46070ee1))
|
||||||
|
* 修复启动时报密钥备份不存在的问题 ([c966896](https://github.com/certd/certd/commit/c9668965226af6b54e0e576931dcba8b3d188ef3))
|
||||||
|
|
||||||
## [1.39.13](https://github.com/certd/certd/compare/v1.39.12...v1.39.13) (2026-05-10)
|
## [1.39.13](https://github.com/certd/certd/compare/v1.39.12...v1.39.13) (2026-05-10)
|
||||||
|
|
||||||
### Bug Fixes
|
### Bug Fixes
|
||||||
|
|||||||
@@ -38,6 +38,7 @@ Certd® 是一个免费的全自动证书管理系统,让你的网站证书永
|
|||||||
* **开放接口支持**: 提供RESTful API接口,方便集成到其他系统
|
* **开放接口支持**: 提供RESTful API接口,方便集成到其他系统
|
||||||
* **站点证书监控**: 定时监控网站证书的过期时间
|
* **站点证书监控**: 定时监控网站证书的过期时间
|
||||||
* **多用户管理**: 用户可以管理自己的证书流水线
|
* **多用户管理**: 用户可以管理自己的证书流水线
|
||||||
|
* **项目管理**: 企业级项目管理模式
|
||||||
* **多语言支持**: 中英双语切换
|
* **多语言支持**: 中英双语切换
|
||||||
* **无忧升级**: 版本向下兼容
|
* **无忧升级**: 版本向下兼容
|
||||||
|
|
||||||
@@ -179,19 +180,23 @@ https://certd.handfree.work/
|
|||||||
|
|
||||||
[50元专业版优惠券限时领取](https://app.handfree.work/subject/#/app/certd/product)
|
[50元专业版优惠券限时领取](https://app.handfree.work/subject/#/app/certd/product)
|
||||||
|
|
||||||
专业版特权对比
|
专业版、商业版特权对比
|
||||||
|
|
||||||
| 功能 | 免费版 | 专业版 |
|
|
||||||
|---------|---------------------------------------|--------------------------------|
|
|
||||||
| 免费证书申请 | 免费无限制 | 免费无限制 |
|
|
||||||
| 证书域名数量 | 无限制 | 无限制 |
|
|
||||||
| 证书流水线条数 | 无限制 | 无限制 |
|
|
||||||
| 自动部署插件 | 阿里云CDN、腾讯云、七牛CDN、主机部署、宝塔、1Panel等大部分插件 | 群晖、威联通、proxmox等 |
|
|
||||||
| 通知 | 邮件通知、自定义webhook | 邮件免配置、企微、钉钉、飞书、anpush、server酱等 |
|
|
||||||
| 站点监控 | 限制1条 | 无限制 |
|
|
||||||
| 批量操作 | 无 | 流水线模版,流水线复制,批量运行,批量设置通知、定时等 |
|
|
||||||
| VIP群 | 无 | 可加,一对一技术支持,必要时可申请远程协助 |
|
|
||||||
|
|
||||||
|
| 功能 | 免费版 | 专业版 | 商业版 |
|
||||||
|
|---------|---------------------------------------|--------------------------------|---------------------------------|
|
||||||
|
| 证书申请 | 无限制 | 无限制 | 无限制 |
|
||||||
|
| 证书域名数量 | 无限制 | 无限制 | 无限制 |
|
||||||
|
| 证书流水线条数 | 无限制 | 无限制 | 无限制 |
|
||||||
|
| 自动部署插件 | 阿里云CDN、腾讯云、七牛CDN、主机部署、宝塔、1Panel等大部分插件 | 群晖、威联通、proxmox等 | 同专业版 |
|
||||||
|
| 通知 | 邮件通知、自定义webhook | 邮件免配置、企微、钉钉、飞书、anpush、server酱等 | 同专业版 |
|
||||||
|
| 站点监控 | 限制1条 | 无限制 | 无限制 |
|
||||||
|
| 批量操作 | 无 | 流水线模版,流水线复制,批量运行,批量设置通知、定时等 | 同专业版 |
|
||||||
|
| VIP群 | 无 | 可加,一对一技术支持,必要时可申请远程协助 | 商业版技术支持 |
|
||||||
|
| 站点个性化 | 无 | 无 | 可自定义站点名称、Logo等,移除Certd元素,首页警告等 |
|
||||||
|
| 套餐功能 | 无 | 无 | 支持配置套餐供用户购买 |
|
||||||
|
| 数据统计 | 无 | 无 | 支持站点各类统计数据 |
|
||||||
|
| 插件管理 | 无 | 无 | 支持公共EAB设置,插件选项配置 |
|
||||||
|
| 是否可商用 | 不允许 | 不允许 | 可对外运营 |
|
||||||
|
|
||||||
## 九、贡献代码
|
## 九、贡献代码
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,95 @@
|
|||||||
|
version: '3.3' # 兼容旧版docker-compose
|
||||||
|
services:
|
||||||
|
certd:
|
||||||
|
# 镜像 # ↓↓↓↓↓ ---- 镜像版本号,建议改成固定版本号,例如:certd:1.29.0
|
||||||
|
image: registry.cn-shenzhen.aliyuncs.com/handsfree/certd:latest
|
||||||
|
# image: ghcr.io/certd/certd:latest # --------- 如果 报镜像not found,可以尝试其他镜像源
|
||||||
|
# image: greper/certd:latest
|
||||||
|
container_name: certd # 容器名
|
||||||
|
restart: unless-stopped # 自动重启
|
||||||
|
volumes:
|
||||||
|
# ↓↓↓↓↓ -------------------------------------------------------- 数据库以及证书存储路径,默认存在宿主机的/data/certd/目录下,【您需要定时备份此目录,以保障数据容灾】
|
||||||
|
- /data/certd:/app/data # 只要修改冒号前面的,冒号后面的/app/data切记切记不要动
|
||||||
|
#- /volume1/docker/certd:/app/data:delegated #群晖使用这个配置
|
||||||
|
# ↓↓↓↓↓ -------------------------------------------------------- 如果走时不准,考虑挂载localtime文件
|
||||||
|
#- /etc/localtime:/etc/localtime
|
||||||
|
#- /etc/timezone:/etc/timezone
|
||||||
|
ports: # 端口映射
|
||||||
|
# ↓↓↓↓ ---------------------------------------------------------- 如果端口有冲突,可以修改第一个7001为其他不冲突的端口号,第二个7001不要动
|
||||||
|
- "7001:7001"
|
||||||
|
# ↓↓↓↓ ---------------------------------------------------------- https端口,可以根据实际情况,是否暴露该端口
|
||||||
|
- "7002:7002"
|
||||||
|
#↓↓↓↓ -------------------------------------------------------------- 如果出现getaddrinfo EAI_AGAIN 或 getaddrinfo ENOTFOUND 错误,可以尝试设置dns
|
||||||
|
# dns:
|
||||||
|
# - 223.5.5.5 # 阿里云公共dns
|
||||||
|
# - 223.6.6.6
|
||||||
|
# # ↓↓↓↓ --------------------------------------------------------- 如果你服务器在腾讯云,可以用这个替换上面阿里云的公共dns
|
||||||
|
# - 119.29.29.29 # 腾讯云公共dns
|
||||||
|
# - 182.254.116.116
|
||||||
|
# # ↓↓↓↓ --------------------------------------------------------- 如果你服务器部署在国外,可以用这个替换上面阿里云的公共dns
|
||||||
|
# - 8.8.8.8 # 谷歌公共dns
|
||||||
|
# - 8.8.4.4
|
||||||
|
# extra_hosts:
|
||||||
|
# # ↓↓↓↓ -------------------------------------------------------- 这里可以配置自定义hosts,外网域名可以指向本地局域网ip地址
|
||||||
|
# - "localdomain.com:192.168.1.3"
|
||||||
|
# # ↓↓↓↓ ------------------------------------------------ 直接使用主机的网络,如果网络问题实在找不到原因,可以尝试打开此参数
|
||||||
|
# network_mode: host
|
||||||
|
labels:
|
||||||
|
com.centurylinklabs.watchtower.enable: "true"
|
||||||
|
# ↓↓↓↓ -------------------------------------------------------------- 启用ipv6网络,还需要把下面networks的注释放开
|
||||||
|
# networks:
|
||||||
|
# - ip6net
|
||||||
|
environment:
|
||||||
|
# ↓↓↓↓ ----------------------------------------------------- 使用上海东八时区
|
||||||
|
- TZ=Asia/Shanghai
|
||||||
|
# 设置环境变量即可自定义certd配置
|
||||||
|
# 配置项见: packages/ui/certd-server/src/config/config.default.ts
|
||||||
|
# 配置规则: certd_ + 配置项, 点号用_代替
|
||||||
|
# #↓↓↓↓ ----------------------------- 如果忘记管理员密码,可以设置为true,docker compose up -d 重建容器之后,管理员密码将改成123456,然后请及时修改回false
|
||||||
|
- certd_system_resetAdminPasswd=false
|
||||||
|
# ↓↓↓ 要使用ipv6,将此配置修改为::
|
||||||
|
- certd_koa_hostname=0.0.0.0
|
||||||
|
|
||||||
|
# 默认使用sqlite文件数据库,如果需要使用其他数据库,请设置以下环境变量
|
||||||
|
# 注意: 选定使用一种数据库之后,不支持更换数据库。
|
||||||
|
# 数据库迁移方法:1、使用新数据库重新部署一套,然后将旧数据同步过去,注意flyway_history表的数据不要同步
|
||||||
|
# #↓↓↓↓ ----------------------------- 使用postgresql数据库,需要提前创建数据库
|
||||||
|
# - certd_flyway_scriptDir=./db/migration-pg # 升级脚本目录
|
||||||
|
# - certd_typeorm_dataSource_default_type=postgres # 数据库类型
|
||||||
|
# - certd_typeorm_dataSource_default_host=localhost # 数据库地址
|
||||||
|
# - certd_typeorm_dataSource_default_port=5433 # 数据库端口
|
||||||
|
# - certd_typeorm_dataSource_default_username=postgres # 用户名
|
||||||
|
# - certd_typeorm_dataSource_default_password=yourpasswd # 密码
|
||||||
|
# - certd_typeorm_dataSource_default_database=certd # 数据库名
|
||||||
|
|
||||||
|
# #↓↓↓↓ ----------------------------- 使用mysql8数据库,需要提前创建数据库 charset=utf8mb4, collation=utf8mb4_bin
|
||||||
|
# - certd_flyway_scriptDir=./db/migration-mysql # 升级脚本目录
|
||||||
|
# - certd_typeorm_dataSource_default_type=mysql # 数据库类型, 或者 mariadb
|
||||||
|
# - certd_typeorm_dataSource_default_host=localhost # 数据库地址
|
||||||
|
# - certd_typeorm_dataSource_default_port=3306 # 数据库端口
|
||||||
|
# - certd_typeorm_dataSource_default_username=root # 用户名
|
||||||
|
# - certd_typeorm_dataSource_default_password=yourpasswd # 密码
|
||||||
|
# - certd_typeorm_dataSource_default_database=certd # 数据库名
|
||||||
|
|
||||||
|
# ↓↓↓↓ --------------------------------------------------------- 自动升级,上面certd的版本号要保持为latest
|
||||||
|
certd-updater: # 添加 Watchtower 服务
|
||||||
|
image: containrrr/watchtower:latest
|
||||||
|
container_name: certd-updater
|
||||||
|
restart: unless-stopped
|
||||||
|
volumes:
|
||||||
|
- /var/run/docker.sock:/var/run/docker.sock
|
||||||
|
# 配置 自动更新
|
||||||
|
environment:
|
||||||
|
- WATCHTOWER_CLEANUP=true # 自动清理旧版本容器
|
||||||
|
- WATCHTOWER_INCLUDE_STOPPED=false # 不更新已停止的容器
|
||||||
|
- WATCHTOWER_LABEL_ENABLE=true # 根据容器标签进行更新
|
||||||
|
- WATCHTOWER_POLL_INTERVAL=600 # 每 10 分钟检查一次更新
|
||||||
|
|
||||||
|
|
||||||
|
# ↓↓↓↓ -------------------------------------------------------------- 启用ipv6网络,还需要把上面networks的注释放开
|
||||||
|
#networks:
|
||||||
|
# ip6net:
|
||||||
|
# enable_ipv6: true
|
||||||
|
# ipam:
|
||||||
|
# config:
|
||||||
|
# - subnet: 2001:db8::/64
|
||||||
@@ -3,6 +3,69 @@
|
|||||||
All notable changes to this project will be documented in this file.
|
All notable changes to this project will be documented in this file.
|
||||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||||
|
|
||||||
|
# [1.40.0](https://github.com/certd/certd/compare/v1.39.16...v1.40.0) (2026-05-14)
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* 修复第三方登录丢失state时无法在用户信息页面绑定第三方账号的bug ([45dedf5](https://github.com/certd/certd/commit/45dedf5bc779fea852e1f33dda4f31db2765633c))
|
||||||
|
* 修复群晖授权没有显示设备id输入框的bug ([2f172b5](https://github.com/certd/certd/commit/2f172b56e9411303ca15138d827bdb9bafdae4d1))
|
||||||
|
* 修复自动注册后没有跳转到控制台的bug ([4681ec9](https://github.com/certd/certd/commit/4681ec90088a3eb665427b2ac4047ec5ccefd7b3))
|
||||||
|
* 修复clogin登录丢失state问题 ([22f5cfc](https://github.com/certd/certd/commit/22f5cfcfd8462ca74128329eefb3f48b3ee0b7ea))
|
||||||
|
* 修复clogin多选类型登录失败的bug ([9f878a3](https://github.com/certd/certd/commit/9f878a353cd49b7b10bb0a95610ad236bc920dd2))
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* 彩虹登录支持选择多种登录方式 ([7aa0c7e](https://github.com/certd/certd/commit/7aa0c7e491fe660abb62e68792ff5474f19bd5b8))
|
||||||
|
|
||||||
|
### Performance Improvements
|
||||||
|
|
||||||
|
* 第三方登录自动注册的用户支持设置初始化密码 ([a815d02](https://github.com/certd/certd/commit/a815d0245b97efbb948b33d6fc9d49862ce06889))
|
||||||
|
* 头像增加缓存时间 ([7015b1b](https://github.com/certd/certd/commit/7015b1b232602e5168a3eb8bee6d7f1776ae1e74))
|
||||||
|
|
||||||
|
## [1.39.16](https://github.com/certd/certd/compare/v1.39.15...v1.39.16) (2026-05-13)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package root
|
||||||
|
|
||||||
|
## [1.39.15](https://github.com/certd/certd/compare/v1.39.14...v1.39.15) (2026-05-13)
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* 修复第三方登录彩虹登录不上的bug ([bae4f8e](https://github.com/certd/certd/commit/bae4f8e3209d9f9869ecbd7c01655383bac2fe21))
|
||||||
|
|
||||||
|
### Performance Improvements
|
||||||
|
|
||||||
|
* 优化申请时报错日志增加对应域名打印 ([d6e9e59](https://github.com/certd/certd/commit/d6e9e5987bd52ea12ee18745615486eadd4c87ff))
|
||||||
|
* icon选择器增加一套logo集 ([fdd5848](https://github.com/certd/certd/commit/fdd5848df4055a6ee07dc5eabaaf6b718672882d))
|
||||||
|
* **monitor/site:** 新增站点监控页面禁用启用、检查状态两个筛选条件 ([118c15d](https://github.com/certd/certd/commit/118c15d04633a6ef06f2d9e7a7849d20f596e02c))
|
||||||
|
* **network:** 新增全局公共http请求 headers设置 ([aad9045](https://github.com/certd/certd/commit/aad9045de55e76cb2ad09cac74a7bd60a4b47124))
|
||||||
|
|
||||||
|
## [1.39.14](https://github.com/certd/certd/compare/v1.39.13...v1.39.14) (2026-05-11)
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* 修复阿里云订阅流水线创建对话框无法获取阿里订单列表的bug ([a362860](https://github.com/certd/certd/commit/a362860137bfb7072893c844fe775edc46070ee1))
|
||||||
|
* 修复启动时报密钥备份不存在的问题 ([c966896](https://github.com/certd/certd/commit/c9668965226af6b54e0e576931dcba8b3d188ef3))
|
||||||
|
|
||||||
|
## [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)
|
## [1.39.12](https://github.com/certd/certd/compare/v1.39.11...v1.39.12) (2026-04-29)
|
||||||
|
|
||||||
### Bug Fixes
|
### Bug Fixes
|
||||||
|
|||||||
+16
-10
@@ -16,16 +16,22 @@
|
|||||||
****------------------****
|
****------------------****
|
||||||
## 专业版特权对比
|
## 专业版特权对比
|
||||||
|
|
||||||
| 功能 | 免费版 | 专业版 |
|
| 功能 | 免费版 | 专业版 | 商业版 |
|
||||||
|---------|---------------------------------------|--------------------------------|
|
|---------|---------------------------------------|--------------------------------|---------------------------------|
|
||||||
| 证书申请 | 无限制 | 无限制 |
|
| 证书申请 | 无限制 | 无限制 | 无限制 |
|
||||||
| 证书域名数量 | 无限制 | 无限制 |
|
| 证书域名数量 | 无限制 | 无限制 | 无限制 |
|
||||||
| 证书流水线条数 | 无限制 | 无限制 |
|
| 证书流水线条数 | 无限制 | 无限制 | 无限制 |
|
||||||
| 自动部署插件 | 阿里云CDN、腾讯云、七牛CDN、主机部署、宝塔、1Panel等大部分插件 | 群晖、威联通、proxmox等 |
|
| 自动部署插件 | 阿里云CDN、腾讯云、七牛CDN、主机部署、宝塔、1Panel等大部分插件 | 群晖、威联通、proxmox等 | 同专业版 |
|
||||||
| 通知 | 邮件通知、自定义webhook | 邮件免配置、企微、钉钉、飞书、anpush、server酱等 |
|
| 通知 | 邮件通知、自定义webhook | 邮件免配置、企微、钉钉、飞书、anpush、server酱等 | 同专业版 |
|
||||||
| 站点监控 | 限制1条 | 无限制 |
|
| 站点监控 | 限制1条 | 无限制 | 无限制 |
|
||||||
| 批量操作 | 无 | 流水线模版,流水线复制,批量运行,批量设置通知、定时等 |
|
| 批量操作 | 无 | 流水线模版,流水线复制,批量运行,批量设置通知、定时等 | 同专业版 |
|
||||||
| VIP群 | 无 | 可加,一对一技术支持,必要时可申请远程协助 |
|
| VIP群 | 无 | 可加,一对一技术支持,必要时可申请远程协助 | 商业版技术支持 |
|
||||||
|
| 站点个性化 | 无 | 无 | 可自定义站点名称、Logo等,移除Certd元素,首页警告等 |
|
||||||
|
| 套餐功能 | 无 | 无 | 支持配置套餐供用户购买 |
|
||||||
|
| 数据统计 | 无 | 无 | 支持站点各类统计数据 |
|
||||||
|
| 插件管理 | 无 | 无 | 支持公共EAB设置,插件选项配置 |
|
||||||
|
| 是否可商用 | 不允许 | 不允许 | 可对外运营 |
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## 专业版激活方式
|
## 专业版激活方式
|
||||||
|
|||||||
@@ -0,0 +1,60 @@
|
|||||||
|
|
||||||
|
## 自动升级配置
|
||||||
|
|
||||||
|
### 1. 方法一:使用watchtower监控自动升级【推荐】
|
||||||
|
|
||||||
|
1. 修改docker-compose.yaml文件增加如下配置
|
||||||
|
或 [下载完整的自动升级docker-compose.yaml配置](https://gitee.com/certd/certd/raw/v2/docker/auto/docker-compose.yaml)
|
||||||
|
```yaml
|
||||||
|
services:
|
||||||
|
certd:
|
||||||
|
# 镜像 # ↓↓↓↓↓ ---- 镜像版本号 这里要保持为latest
|
||||||
|
image: registry.cn-shenzhen.aliyuncs.com/handsfree/certd:latest
|
||||||
|
... # 这里是你原来的docker-compose.yaml配置
|
||||||
|
|
||||||
|
# ↓↓↓↓ --------------------------------------------------------- 增加一个标签,表示certd需要自动升级
|
||||||
|
labels:
|
||||||
|
com.centurylinklabs.watchtower.enable: "true"
|
||||||
|
|
||||||
|
# ↓↓↓↓ --------------------------------------------------------- 自动升级watchtower配置,注意:上面certd的版本号要保持为latest
|
||||||
|
certd-updater: # 添加 Watchtower 服务
|
||||||
|
image: containrrr/watchtower:latest
|
||||||
|
container_name: certd-updater
|
||||||
|
restart: unless-stopped
|
||||||
|
volumes:
|
||||||
|
- /var/run/docker.sock:/var/run/docker.sock
|
||||||
|
# 配置 自动更新
|
||||||
|
environment:
|
||||||
|
- WATCHTOWER_CLEANUP=true # 自动清理旧版本容器
|
||||||
|
- WATCHTOWER_INCLUDE_STOPPED=false # 不更新已停止的容器
|
||||||
|
- WATCHTOWER_LABEL_ENABLE=true # 根据容器标签进行更新
|
||||||
|
- WATCHTOWER_POLL_INTERVAL=600 # 每 10 分钟检查一次更新
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
2. 重启certd容器
|
||||||
|
```shell
|
||||||
|
cd certd
|
||||||
|
docker compose down
|
||||||
|
docker compose up -d
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
### 2. 方法二:使用Certd版本监控功能【不太稳定】
|
||||||
|
|
||||||
|
1. 选择Github-检查Release版本插件
|
||||||
|

|
||||||
|
按如下图填写配置
|
||||||
|

|
||||||
|
|
||||||
|
|
||||||
|
2. 检测到新版本后执行宿主机升级命令:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
# 拉取最新镜像
|
||||||
|
docker pull registry.cn-shenzhen.aliyuncs.com/handsfree/certd:latest
|
||||||
|
# 升级容器命令, 替换成你自己的certd更新命令
|
||||||
|
export RESTART_CERT='sleep 10; cd ~/deploy/certd/ ; docker compose down; docker compose up -d'
|
||||||
|
# 构造一个脚本10s后在后台执行,避免容器销毁时执行太快,导致流水线任务无法结束
|
||||||
|
nohup sh -c '$RESTART_CERT' >/dev/null 2>&1 & echo '10秒后重启' && exit
|
||||||
|
```
|
||||||
@@ -22,51 +22,3 @@
|
|||||||
可以查看最新版本号,以及所有版本的更新日志
|
可以查看最新版本号,以及所有版本的更新日志
|
||||||
[CHANGELOG](../changelogs/CHANGELOG.md)
|
[CHANGELOG](../changelogs/CHANGELOG.md)
|
||||||
|
|
||||||
|
|
||||||
## 自动升级配置
|
|
||||||
|
|
||||||
### 1. 方法一:使用watchtower监控
|
|
||||||
|
|
||||||
修改docker-compose.yaml文件增加如下配置, 使用watchtower监控自动升级
|
|
||||||
```yaml
|
|
||||||
services:
|
|
||||||
certd:
|
|
||||||
...
|
|
||||||
labels:
|
|
||||||
com.centurylinklabs.watchtower.enable: "true"
|
|
||||||
|
|
||||||
# ↓↓↓↓ --------------------------------------------------------- 自动升级,上面certd的版本号要保持为latest
|
|
||||||
certd-updater: # 添加 Watchtower 服务
|
|
||||||
image: containrrr/watchtower:latest
|
|
||||||
container_name: certd-updater
|
|
||||||
restart: unless-stopped
|
|
||||||
volumes:
|
|
||||||
- /var/run/docker.sock:/var/run/docker.sock
|
|
||||||
# 配置 自动更新
|
|
||||||
environment:
|
|
||||||
- WATCHTOWER_CLEANUP=true # 自动清理旧版本容器
|
|
||||||
- WATCHTOWER_INCLUDE_STOPPED=false # 不更新已停止的容器
|
|
||||||
- WATCHTOWER_LABEL_ENABLE=true # 根据容器标签进行更新
|
|
||||||
- WATCHTOWER_POLL_INTERVAL=600 # 每 10 分钟检查一次更新
|
|
||||||
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
### 2. 方法二:使用Certd版本监控功能
|
|
||||||
|
|
||||||
选择Github-检查Release版本插件
|
|
||||||

|
|
||||||
按如下图填写配置
|
|
||||||

|
|
||||||
|
|
||||||
|
|
||||||
检测到新版本后执行宿主机升级命令:
|
|
||||||
|
|
||||||
```shell
|
|
||||||
# 拉取最新镜像
|
|
||||||
docker pull registry.cn-shenzhen.aliyuncs.com/handsfree/certd:latest
|
|
||||||
# 升级容器命令, 替换成你自己的certd更新命令
|
|
||||||
export RESTART_CERT='sleep 10; cd ~/deploy/certd/ ; docker compose down; docker compose up -d'
|
|
||||||
# 构造一个脚本10s后在后台执行,避免容器销毁时执行太快,导致流水线任务无法结束
|
|
||||||
nohup sh -c '$RESTART_CERT' >/dev/null 2>&1 & echo '10秒后重启' && exit
|
|
||||||
```
|
|
||||||
+1
-1
@@ -9,5 +9,5 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"npmClient": "pnpm",
|
"npmClient": "pnpm",
|
||||||
"version": "1.39.13"
|
"version": "1.40.0"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,24 @@
|
|||||||
All notable changes to this project will be documented in this file.
|
All notable changes to this project will be documented in this file.
|
||||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||||
|
|
||||||
|
# [1.40.0](https://github.com/publishlab/node-acme-client/compare/v1.39.16...v1.40.0) (2026-05-14)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @certd/acme-client
|
||||||
|
|
||||||
|
## [1.39.16](https://github.com/publishlab/node-acme-client/compare/v1.39.15...v1.39.16) (2026-05-13)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @certd/acme-client
|
||||||
|
|
||||||
|
## [1.39.15](https://github.com/publishlab/node-acme-client/compare/v1.39.14...v1.39.15) (2026-05-13)
|
||||||
|
|
||||||
|
### Performance Improvements
|
||||||
|
|
||||||
|
* 优化申请时报错日志增加对应域名打印 ([d6e9e59](https://github.com/publishlab/node-acme-client/commit/d6e9e5987bd52ea12ee18745615486eadd4c87ff))
|
||||||
|
|
||||||
|
## [1.39.14](https://github.com/publishlab/node-acme-client/compare/v1.39.13...v1.39.14) (2026-05-11)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @certd/acme-client
|
||||||
|
|
||||||
## [1.39.13](https://github.com/publishlab/node-acme-client/compare/v1.39.12...v1.39.13) (2026-05-10)
|
## [1.39.13](https://github.com/publishlab/node-acme-client/compare/v1.39.12...v1.39.13) (2026-05-10)
|
||||||
|
|
||||||
### Performance Improvements
|
### Performance Improvements
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
"description": "Simple and unopinionated ACME client",
|
"description": "Simple and unopinionated ACME client",
|
||||||
"private": false,
|
"private": false,
|
||||||
"author": "nmorsman",
|
"author": "nmorsman",
|
||||||
"version": "1.39.13",
|
"version": "1.40.0",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"module": "./dist/index.js",
|
"module": "./dist/index.js",
|
||||||
"main": "./dist/index.js",
|
"main": "./dist/index.js",
|
||||||
@@ -18,7 +18,7 @@
|
|||||||
"types"
|
"types"
|
||||||
],
|
],
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@certd/basic": "^1.39.13",
|
"@certd/basic": "^1.40.0",
|
||||||
"@peculiar/x509": "^1.11.0",
|
"@peculiar/x509": "^1.11.0",
|
||||||
"asn1js": "^3.0.5",
|
"asn1js": "^3.0.5",
|
||||||
"axios": "^1.9.0",
|
"axios": "^1.9.0",
|
||||||
@@ -76,5 +76,5 @@
|
|||||||
"bugs": {
|
"bugs": {
|
||||||
"url": "https://github.com/publishlab/node-acme-client/issues"
|
"url": "https://github.com/publishlab/node-acme-client/issues"
|
||||||
},
|
},
|
||||||
"gitHead": "898bc9b9f2f75df11ea0803b144862ba98b7511a"
|
"gitHead": "5801f34b3a40cbbd591805e401613b397bec9775"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -167,6 +167,7 @@ export default async (client, userOpts) => {
|
|||||||
await client.completeChallenge(challenge);
|
await client.completeChallenge(challenge);
|
||||||
}catch (e) {
|
}catch (e) {
|
||||||
await deactivateAuth(e);
|
await deactivateAuth(e);
|
||||||
|
e.message = `[${d}] ${e.message || "completeChallenge error"}`;
|
||||||
throw e;
|
throw e;
|
||||||
}
|
}
|
||||||
challengeCompleted = true;
|
challengeCompleted = true;
|
||||||
@@ -178,6 +179,7 @@ export default async (client, userOpts) => {
|
|||||||
} catch (e) {
|
} catch (e) {
|
||||||
log(`[auto] [${d}] challengeCreateFn threw error: ${e.message || e}`);
|
log(`[auto] [${d}] challengeCreateFn threw error: ${e.message || e}`);
|
||||||
await deactivateAuth(e);
|
await deactivateAuth(e);
|
||||||
|
e.message = `[${d}] ${e.message || "challengeCreateFn error"}`;
|
||||||
throw e;
|
throw e;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,24 @@
|
|||||||
All notable changes to this project will be documented in this file.
|
All notable changes to this project will be documented in this file.
|
||||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||||
|
|
||||||
|
# [1.40.0](https://github.com/certd/certd/compare/v1.39.16...v1.40.0) (2026-05-14)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @certd/basic
|
||||||
|
|
||||||
|
## [1.39.16](https://github.com/certd/certd/compare/v1.39.15...v1.39.16) (2026-05-13)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @certd/basic
|
||||||
|
|
||||||
|
## [1.39.15](https://github.com/certd/certd/compare/v1.39.14...v1.39.15) (2026-05-13)
|
||||||
|
|
||||||
|
### Performance Improvements
|
||||||
|
|
||||||
|
* **network:** 新增全局公共http请求 headers设置 ([aad9045](https://github.com/certd/certd/commit/aad9045de55e76cb2ad09cac74a7bd60a4b47124))
|
||||||
|
|
||||||
|
## [1.39.14](https://github.com/certd/certd/compare/v1.39.13...v1.39.14) (2026-05-11)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @certd/basic
|
||||||
|
|
||||||
## [1.39.13](https://github.com/certd/certd/compare/v1.39.12...v1.39.13) (2026-05-10)
|
## [1.39.13](https://github.com/certd/certd/compare/v1.39.12...v1.39.13) (2026-05-10)
|
||||||
|
|
||||||
### Performance Improvements
|
### Performance Improvements
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
00:21
|
00:44
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "@certd/basic",
|
"name": "@certd/basic",
|
||||||
"private": false,
|
"private": false,
|
||||||
"version": "1.39.13",
|
"version": "1.40.0",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "./dist/index.js",
|
"main": "./dist/index.js",
|
||||||
"module": "./dist/index.js",
|
"module": "./dist/index.js",
|
||||||
@@ -52,5 +52,5 @@
|
|||||||
"tslib": "^2.8.1",
|
"tslib": "^2.8.1",
|
||||||
"typescript": "^5.4.2"
|
"typescript": "^5.4.2"
|
||||||
},
|
},
|
||||||
"gitHead": "898bc9b9f2f75df11ea0803b144862ba98b7511a"
|
"gitHead": "5801f34b3a40cbbd591805e401613b397bec9775"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,53 @@
|
|||||||
|
import { expect } from "chai";
|
||||||
|
import { createAxiosService, HttpClient, setGlobalHeaders } from "./util.request.js";
|
||||||
|
import { ILogger } from "./util.log.js";
|
||||||
|
|
||||||
|
const testLogger = {
|
||||||
|
info() {},
|
||||||
|
error() {},
|
||||||
|
} as unknown as ILogger;
|
||||||
|
|
||||||
|
describe("util.request", () => {
|
||||||
|
afterEach(() => {
|
||||||
|
setGlobalHeaders({});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should merge global headers without overriding request headers", async () => {
|
||||||
|
setGlobalHeaders({
|
||||||
|
"X-Common": "common",
|
||||||
|
"X-Override": "global",
|
||||||
|
});
|
||||||
|
|
||||||
|
const http = createAxiosService({ logger: testLogger }) as HttpClient;
|
||||||
|
const res = await http.request({
|
||||||
|
url: "http://example.com",
|
||||||
|
method: "get",
|
||||||
|
logReq: false,
|
||||||
|
logRes: false,
|
||||||
|
headers: {
|
||||||
|
"X-Override": "request",
|
||||||
|
"X-Request": "request",
|
||||||
|
},
|
||||||
|
adapter: async config => {
|
||||||
|
const headers = config.headers;
|
||||||
|
return {
|
||||||
|
config,
|
||||||
|
data: {
|
||||||
|
common: headers.get("X-Common"),
|
||||||
|
override: headers.get("X-Override"),
|
||||||
|
request: headers.get("X-Request"),
|
||||||
|
},
|
||||||
|
headers: {},
|
||||||
|
status: 200,
|
||||||
|
statusText: "OK",
|
||||||
|
};
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(res).to.deep.equal({
|
||||||
|
common: "common",
|
||||||
|
override: "request",
|
||||||
|
request: "request",
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -82,6 +82,7 @@ export class HttpError extends Error {
|
|||||||
export const HttpCommonError = HttpError;
|
export const HttpCommonError = HttpError;
|
||||||
|
|
||||||
let defaultAgents = createAgent();
|
let defaultAgents = createAgent();
|
||||||
|
let defaultHeaders: Record<string, string> = {};
|
||||||
|
|
||||||
export function setGlobalProxy(opts: { httpProxy?: string; httpsProxy?: string }) {
|
export function setGlobalProxy(opts: { httpProxy?: string; httpsProxy?: string }) {
|
||||||
logger.info("setGlobalProxy:", opts);
|
logger.info("setGlobalProxy:", opts);
|
||||||
@@ -92,6 +93,15 @@ export function getGlobalAgents() {
|
|||||||
return defaultAgents;
|
return defaultAgents;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function setGlobalHeaders(headers: Record<string, string> = {}) {
|
||||||
|
logger.info("setGlobalHeaders:", Object.keys(headers));
|
||||||
|
defaultHeaders = { ...headers };
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getGlobalHeaders() {
|
||||||
|
return defaultHeaders;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @description 创建请求实例
|
* @description 创建请求实例
|
||||||
*/
|
*/
|
||||||
@@ -148,6 +158,12 @@ export function createAxiosService({ logger }: { logger: ILogger }) {
|
|||||||
config.httpsAgent = agents.httpsAgent;
|
config.httpsAgent = agents.httpsAgent;
|
||||||
config.httpAgent = agents.httpAgent;
|
config.httpAgent = agents.httpAgent;
|
||||||
|
|
||||||
|
if (Object.keys(defaultHeaders).length > 0) {
|
||||||
|
const headers = AxiosHeaders.from(defaultHeaders);
|
||||||
|
headers.set(config.headers || {});
|
||||||
|
config.headers = headers;
|
||||||
|
}
|
||||||
|
|
||||||
// const agent = new https.Agent({
|
// const agent = new https.Agent({
|
||||||
// rejectUnauthorized: false // 允许自签名证书
|
// rejectUnauthorized: false // 允许自签名证书
|
||||||
// });
|
// });
|
||||||
|
|||||||
@@ -3,6 +3,22 @@
|
|||||||
All notable changes to this project will be documented in this file.
|
All notable changes to this project will be documented in this file.
|
||||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||||
|
|
||||||
|
# [1.40.0](https://github.com/certd/certd/compare/v1.39.16...v1.40.0) (2026-05-14)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @certd/pipeline
|
||||||
|
|
||||||
|
## [1.39.16](https://github.com/certd/certd/compare/v1.39.15...v1.39.16) (2026-05-13)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @certd/pipeline
|
||||||
|
|
||||||
|
## [1.39.15](https://github.com/certd/certd/compare/v1.39.14...v1.39.15) (2026-05-13)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @certd/pipeline
|
||||||
|
|
||||||
|
## [1.39.14](https://github.com/certd/certd/compare/v1.39.13...v1.39.14) (2026-05-11)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @certd/pipeline
|
||||||
|
|
||||||
## [1.39.13](https://github.com/certd/certd/compare/v1.39.12...v1.39.13) (2026-05-10)
|
## [1.39.13](https://github.com/certd/certd/compare/v1.39.12...v1.39.13) (2026-05-10)
|
||||||
|
|
||||||
### Bug Fixes
|
### Bug Fixes
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "@certd/pipeline",
|
"name": "@certd/pipeline",
|
||||||
"private": false,
|
"private": false,
|
||||||
"version": "1.39.13",
|
"version": "1.40.0",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "./dist/index.js",
|
"main": "./dist/index.js",
|
||||||
"module": "./dist/index.js",
|
"module": "./dist/index.js",
|
||||||
@@ -19,8 +19,8 @@
|
|||||||
"compile": "tsc --skipLibCheck --watch"
|
"compile": "tsc --skipLibCheck --watch"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@certd/basic": "^1.39.13",
|
"@certd/basic": "^1.40.0",
|
||||||
"@certd/plus-core": "^1.39.13",
|
"@certd/plus-core": "^1.40.0",
|
||||||
"dayjs": "^1.11.7",
|
"dayjs": "^1.11.7",
|
||||||
"lodash-es": "^4.17.21",
|
"lodash-es": "^4.17.21",
|
||||||
"reflect-metadata": "^0.1.13"
|
"reflect-metadata": "^0.1.13"
|
||||||
@@ -49,5 +49,5 @@
|
|||||||
"tslib": "^2.8.1",
|
"tslib": "^2.8.1",
|
||||||
"typescript": "^5.4.2"
|
"typescript": "^5.4.2"
|
||||||
},
|
},
|
||||||
"gitHead": "898bc9b9f2f75df11ea0803b144862ba98b7511a"
|
"gitHead": "5801f34b3a40cbbd591805e401613b397bec9775"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,18 @@
|
|||||||
All notable changes to this project will be documented in this file.
|
All notable changes to this project will be documented in this file.
|
||||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||||
|
|
||||||
|
# [1.40.0](https://github.com/certd/certd/compare/v1.39.16...v1.40.0) (2026-05-14)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @certd/lib-huawei
|
||||||
|
|
||||||
|
## [1.39.15](https://github.com/certd/certd/compare/v1.39.14...v1.39.15) (2026-05-13)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @certd/lib-huawei
|
||||||
|
|
||||||
|
## [1.39.14](https://github.com/certd/certd/compare/v1.39.13...v1.39.14) (2026-05-11)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @certd/lib-huawei
|
||||||
|
|
||||||
## [1.39.13](https://github.com/certd/certd/compare/v1.39.12...v1.39.13) (2026-05-10)
|
## [1.39.13](https://github.com/certd/certd/compare/v1.39.12...v1.39.13) (2026-05-10)
|
||||||
|
|
||||||
### Performance Improvements
|
### Performance Improvements
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "@certd/lib-huawei",
|
"name": "@certd/lib-huawei",
|
||||||
"private": false,
|
"private": false,
|
||||||
"version": "1.39.13",
|
"version": "1.40.0",
|
||||||
"main": "./dist/bundle.js",
|
"main": "./dist/bundle.js",
|
||||||
"module": "./dist/bundle.js",
|
"module": "./dist/bundle.js",
|
||||||
"types": "./dist/d/index.d.ts",
|
"types": "./dist/d/index.d.ts",
|
||||||
@@ -27,5 +27,5 @@
|
|||||||
"prettier": "^2.8.8",
|
"prettier": "^2.8.8",
|
||||||
"tslib": "^2.8.1"
|
"tslib": "^2.8.1"
|
||||||
},
|
},
|
||||||
"gitHead": "898bc9b9f2f75df11ea0803b144862ba98b7511a"
|
"gitHead": "5801f34b3a40cbbd591805e401613b397bec9775"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,18 @@
|
|||||||
All notable changes to this project will be documented in this file.
|
All notable changes to this project will be documented in this file.
|
||||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||||
|
|
||||||
|
# [1.40.0](https://github.com/certd/certd/compare/v1.39.16...v1.40.0) (2026-05-14)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @certd/lib-iframe
|
||||||
|
|
||||||
|
## [1.39.15](https://github.com/certd/certd/compare/v1.39.14...v1.39.15) (2026-05-13)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @certd/lib-iframe
|
||||||
|
|
||||||
|
## [1.39.14](https://github.com/certd/certd/compare/v1.39.13...v1.39.14) (2026-05-11)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @certd/lib-iframe
|
||||||
|
|
||||||
## [1.39.13](https://github.com/certd/certd/compare/v1.39.12...v1.39.13) (2026-05-10)
|
## [1.39.13](https://github.com/certd/certd/compare/v1.39.12...v1.39.13) (2026-05-10)
|
||||||
|
|
||||||
### Performance Improvements
|
### Performance Improvements
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "@certd/lib-iframe",
|
"name": "@certd/lib-iframe",
|
||||||
"private": false,
|
"private": false,
|
||||||
"version": "1.39.13",
|
"version": "1.40.0",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "./dist/index.js",
|
"main": "./dist/index.js",
|
||||||
"module": "./dist/index.js",
|
"module": "./dist/index.js",
|
||||||
@@ -34,5 +34,5 @@
|
|||||||
"tslib": "^2.8.1",
|
"tslib": "^2.8.1",
|
||||||
"typescript": "^5.4.2"
|
"typescript": "^5.4.2"
|
||||||
},
|
},
|
||||||
"gitHead": "898bc9b9f2f75df11ea0803b144862ba98b7511a"
|
"gitHead": "5801f34b3a40cbbd591805e401613b397bec9775"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,18 @@
|
|||||||
All notable changes to this project will be documented in this file.
|
All notable changes to this project will be documented in this file.
|
||||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||||
|
|
||||||
|
# [1.40.0](https://github.com/certd/certd/compare/v1.39.16...v1.40.0) (2026-05-14)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @certd/jdcloud
|
||||||
|
|
||||||
|
## [1.39.15](https://github.com/certd/certd/compare/v1.39.14...v1.39.15) (2026-05-13)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @certd/jdcloud
|
||||||
|
|
||||||
|
## [1.39.14](https://github.com/certd/certd/compare/v1.39.13...v1.39.14) (2026-05-11)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @certd/jdcloud
|
||||||
|
|
||||||
## [1.39.13](https://github.com/certd/certd/compare/v1.39.12...v1.39.13) (2026-05-10)
|
## [1.39.13](https://github.com/certd/certd/compare/v1.39.12...v1.39.13) (2026-05-10)
|
||||||
|
|
||||||
### Performance Improvements
|
### Performance Improvements
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@certd/jdcloud",
|
"name": "@certd/jdcloud",
|
||||||
"version": "1.39.13",
|
"version": "1.40.0",
|
||||||
"description": "jdcloud openApi sdk",
|
"description": "jdcloud openApi sdk",
|
||||||
"main": "./dist/bundle.js",
|
"main": "./dist/bundle.js",
|
||||||
"module": "./dist/bundle.js",
|
"module": "./dist/bundle.js",
|
||||||
@@ -58,5 +58,5 @@
|
|||||||
"fetch"
|
"fetch"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"gitHead": "898bc9b9f2f75df11ea0803b144862ba98b7511a"
|
"gitHead": "5801f34b3a40cbbd591805e401613b397bec9775"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,22 @@
|
|||||||
All notable changes to this project will be documented in this file.
|
All notable changes to this project will be documented in this file.
|
||||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||||
|
|
||||||
|
# [1.40.0](https://github.com/certd/certd/compare/v1.39.16...v1.40.0) (2026-05-14)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @certd/lib-k8s
|
||||||
|
|
||||||
|
## [1.39.16](https://github.com/certd/certd/compare/v1.39.15...v1.39.16) (2026-05-13)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @certd/lib-k8s
|
||||||
|
|
||||||
|
## [1.39.15](https://github.com/certd/certd/compare/v1.39.14...v1.39.15) (2026-05-13)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @certd/lib-k8s
|
||||||
|
|
||||||
|
## [1.39.14](https://github.com/certd/certd/compare/v1.39.13...v1.39.14) (2026-05-11)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @certd/lib-k8s
|
||||||
|
|
||||||
## [1.39.13](https://github.com/certd/certd/compare/v1.39.12...v1.39.13) (2026-05-10)
|
## [1.39.13](https://github.com/certd/certd/compare/v1.39.12...v1.39.13) (2026-05-10)
|
||||||
|
|
||||||
### Performance Improvements
|
### Performance Improvements
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "@certd/lib-k8s",
|
"name": "@certd/lib-k8s",
|
||||||
"private": false,
|
"private": false,
|
||||||
"version": "1.39.13",
|
"version": "1.40.0",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "./dist/index.js",
|
"main": "./dist/index.js",
|
||||||
"module": "./dist/index.js",
|
"module": "./dist/index.js",
|
||||||
@@ -19,7 +19,7 @@
|
|||||||
"compile": "tsc --skipLibCheck --watch"
|
"compile": "tsc --skipLibCheck --watch"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@certd/basic": "^1.39.13",
|
"@certd/basic": "^1.40.0",
|
||||||
"@kubernetes/client-node": "0.21.0"
|
"@kubernetes/client-node": "0.21.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
@@ -36,5 +36,5 @@
|
|||||||
"tslib": "^2.8.1",
|
"tslib": "^2.8.1",
|
||||||
"typescript": "^5.4.2"
|
"typescript": "^5.4.2"
|
||||||
},
|
},
|
||||||
"gitHead": "898bc9b9f2f75df11ea0803b144862ba98b7511a"
|
"gitHead": "5801f34b3a40cbbd591805e401613b397bec9775"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,28 @@
|
|||||||
All notable changes to this project will be documented in this file.
|
All notable changes to this project will be documented in this file.
|
||||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||||
|
|
||||||
|
# [1.40.0](https://github.com/certd/certd/compare/v1.39.16...v1.40.0) (2026-05-14)
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* 彩虹登录支持选择多种登录方式 ([7aa0c7e](https://github.com/certd/certd/commit/7aa0c7e491fe660abb62e68792ff5474f19bd5b8))
|
||||||
|
|
||||||
|
## [1.39.16](https://github.com/certd/certd/compare/v1.39.15...v1.39.16) (2026-05-13)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @certd/lib-server
|
||||||
|
|
||||||
|
## [1.39.15](https://github.com/certd/certd/compare/v1.39.14...v1.39.15) (2026-05-13)
|
||||||
|
|
||||||
|
### Performance Improvements
|
||||||
|
|
||||||
|
* **network:** 新增全局公共http请求 headers设置 ([aad9045](https://github.com/certd/certd/commit/aad9045de55e76cb2ad09cac74a7bd60a4b47124))
|
||||||
|
|
||||||
|
## [1.39.14](https://github.com/certd/certd/compare/v1.39.13...v1.39.14) (2026-05-11)
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* 修复启动时报密钥备份不存在的问题 ([c966896](https://github.com/certd/certd/commit/c9668965226af6b54e0e576931dcba8b3d188ef3))
|
||||||
|
|
||||||
## [1.39.13](https://github.com/certd/certd/compare/v1.39.12...v1.39.13) (2026-05-10)
|
## [1.39.13](https://github.com/certd/certd/compare/v1.39.12...v1.39.13) (2026-05-10)
|
||||||
|
|
||||||
### Performance Improvements
|
### Performance Improvements
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@certd/lib-server",
|
"name": "@certd/lib-server",
|
||||||
"version": "1.39.13",
|
"version": "1.40.0",
|
||||||
"description": "midway with flyway, sql upgrade way ",
|
"description": "midway with flyway, sql upgrade way ",
|
||||||
"private": false,
|
"private": false,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
@@ -29,11 +29,11 @@
|
|||||||
],
|
],
|
||||||
"license": "AGPL",
|
"license": "AGPL",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@certd/acme-client": "^1.39.13",
|
"@certd/acme-client": "^1.40.0",
|
||||||
"@certd/basic": "^1.39.13",
|
"@certd/basic": "^1.40.0",
|
||||||
"@certd/pipeline": "^1.39.13",
|
"@certd/pipeline": "^1.40.0",
|
||||||
"@certd/plugin-lib": "^1.39.13",
|
"@certd/plugin-lib": "^1.40.0",
|
||||||
"@certd/plus-core": "^1.39.13",
|
"@certd/plus-core": "^1.40.0",
|
||||||
"@midwayjs/cache": "3.14.0",
|
"@midwayjs/cache": "3.14.0",
|
||||||
"@midwayjs/core": "3.20.11",
|
"@midwayjs/core": "3.20.11",
|
||||||
"@midwayjs/i18n": "3.20.13",
|
"@midwayjs/i18n": "3.20.13",
|
||||||
@@ -69,5 +69,5 @@
|
|||||||
"typeorm": "^0.3.11",
|
"typeorm": "^0.3.11",
|
||||||
"typescript": "^5.4.2"
|
"typescript": "^5.4.2"
|
||||||
},
|
},
|
||||||
"gitHead": "898bc9b9f2f75df11ea0803b144862ba98b7511a"
|
"gitHead": "5801f34b3a40cbbd591805e401613b397bec9775"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -64,6 +64,7 @@ export class SysPublicSettings extends BaseSettings {
|
|||||||
type: string;
|
type: string;
|
||||||
title: string;
|
title: string;
|
||||||
addonId: number;
|
addonId: number;
|
||||||
|
icon?: string;
|
||||||
}> = {};
|
}> = {};
|
||||||
|
|
||||||
notice?: string;
|
notice?: string;
|
||||||
@@ -80,6 +81,7 @@ export class SysPrivateSettings extends BaseSettings {
|
|||||||
|
|
||||||
httpsProxy? = '';
|
httpsProxy? = '';
|
||||||
httpProxy? = '';
|
httpProxy? = '';
|
||||||
|
commonHeaders?: string = '';
|
||||||
|
|
||||||
reverseProxies?: Record<string, string> = {};
|
reverseProxies?: Record<string, string> = {};
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import { SysSettingsEntity } from '../entity/sys-settings.js';
|
|||||||
import { BaseSettings, SysInstallInfo, SysPrivateSettings, SysPublicSettings, SysSecret, SysSecretBackup } from './models.js';
|
import { BaseSettings, SysInstallInfo, SysPrivateSettings, SysPublicSettings, SysSecret, SysSecretBackup } from './models.js';
|
||||||
|
|
||||||
import { getAllSslProviderDomains, setSslProviderReverseProxies, setWalkFromAuthoritative } from '@certd/acme-client';
|
import { getAllSslProviderDomains, setSslProviderReverseProxies, setWalkFromAuthoritative } from '@certd/acme-client';
|
||||||
import { cache, logger, mergeUtils, setGlobalProxy } from '@certd/basic';
|
import { cache, logger, mergeUtils, setGlobalHeaders, setGlobalProxy } from '@certd/basic';
|
||||||
import { isPlus } from '@certd/plus-core';
|
import { isPlus } from '@certd/plus-core';
|
||||||
import * as dns from 'node:dns';
|
import * as dns from 'node:dns';
|
||||||
import { BaseService, setAdminMode } from '../../../basic/index.js';
|
import { BaseService, setAdminMode } from '../../../basic/index.js';
|
||||||
@@ -167,6 +167,7 @@ export class SysSettingsService extends BaseService<SysSettingsEntity> {
|
|||||||
httpsProxy: privateSetting.httpsProxy,
|
httpsProxy: privateSetting.httpsProxy,
|
||||||
};
|
};
|
||||||
setGlobalProxy(opts);
|
setGlobalProxy(opts);
|
||||||
|
setGlobalHeaders(this.parseKeyValueText(privateSetting.commonHeaders));
|
||||||
|
|
||||||
if (privateSetting.dnsResultOrder) {
|
if (privateSetting.dnsResultOrder) {
|
||||||
dns.setDefaultResultOrder(privateSetting.dnsResultOrder as any);
|
dns.setDefaultResultOrder(privateSetting.dnsResultOrder as any);
|
||||||
@@ -185,12 +186,12 @@ export class SysSettingsService extends BaseService<SysSettingsEntity> {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
setEnvironmentVars(vars: string) {
|
parseKeyValueText(text: string) {
|
||||||
const envVars = {}
|
const values = {};
|
||||||
if (typeof vars !== 'string') {
|
if (typeof text !== 'string') {
|
||||||
vars = ""
|
text = "";
|
||||||
}
|
}
|
||||||
vars.split('\n').forEach(line => {
|
text.split('\n').forEach(line => {
|
||||||
line = line.trim();
|
line = line.trim();
|
||||||
if (!line || line.startsWith('#')) {
|
if (!line || line.startsWith('#')) {
|
||||||
return
|
return
|
||||||
@@ -204,11 +205,18 @@ export class SysSettingsService extends BaseService<SysSettingsEntity> {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const [key, value] = line.split('=');
|
const eqIndex = line.indexOf('=');
|
||||||
|
const key = line.substring(0, eqIndex).trim();
|
||||||
|
const value = line.substring(eqIndex + 1).trim();
|
||||||
if (key && value) {
|
if (key && value) {
|
||||||
envVars[key.trim()] = value.trim();
|
values[key] = value;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
return values;
|
||||||
|
}
|
||||||
|
|
||||||
|
setEnvironmentVars(vars: string) {
|
||||||
|
const envVars = this.parseKeyValueText(vars);
|
||||||
//先删除旧环境变量
|
//先删除旧环境变量
|
||||||
if (lastSaveEnvVars) {
|
if (lastSaveEnvVars) {
|
||||||
for (const key in lastSaveEnvVars) {
|
for (const key in lastSaveEnvVars) {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { Init, Inject, Provide, Scope, ScopeEnum } from '@midwayjs/core';
|
import { Inject, Provide, Scope, ScopeEnum } from '@midwayjs/core';
|
||||||
import { Encryptor, SysSecret, SysSettingsService } from '../../../system/index.js';
|
import { Encryptor, SysSecret, SysSettingsService } from '../../../system/index.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -12,8 +12,7 @@ export class EncryptService {
|
|||||||
@Inject()
|
@Inject()
|
||||||
sysSettingService: SysSettingsService;
|
sysSettingService: SysSettingsService;
|
||||||
|
|
||||||
@Init()
|
async doInit() {
|
||||||
async init() {
|
|
||||||
const secret: SysSecret = await this.sysSettingService.getSecret();
|
const secret: SysSecret = await this.sysSettingService.getSecret();
|
||||||
this.encryptor = new Encryptor(secret.encryptSecret);
|
this.encryptor = new Encryptor(secret.encryptSecret);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,18 @@
|
|||||||
All notable changes to this project will be documented in this file.
|
All notable changes to this project will be documented in this file.
|
||||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||||
|
|
||||||
|
# [1.40.0](https://github.com/certd/certd/compare/v1.39.16...v1.40.0) (2026-05-14)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @certd/midway-flyway-js
|
||||||
|
|
||||||
|
## [1.39.15](https://github.com/certd/certd/compare/v1.39.14...v1.39.15) (2026-05-13)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @certd/midway-flyway-js
|
||||||
|
|
||||||
|
## [1.39.14](https://github.com/certd/certd/compare/v1.39.13...v1.39.14) (2026-05-11)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @certd/midway-flyway-js
|
||||||
|
|
||||||
## [1.39.13](https://github.com/certd/certd/compare/v1.39.12...v1.39.13) (2026-05-10)
|
## [1.39.13](https://github.com/certd/certd/compare/v1.39.12...v1.39.13) (2026-05-10)
|
||||||
|
|
||||||
### Performance Improvements
|
### Performance Improvements
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@certd/midway-flyway-js",
|
"name": "@certd/midway-flyway-js",
|
||||||
"version": "1.39.13",
|
"version": "1.40.0",
|
||||||
"description": "midway with flyway, sql upgrade way ",
|
"description": "midway with flyway, sql upgrade way ",
|
||||||
"private": false,
|
"private": false,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
@@ -49,5 +49,5 @@
|
|||||||
"typeorm": "^0.3.11",
|
"typeorm": "^0.3.11",
|
||||||
"typescript": "^5.4.2"
|
"typescript": "^5.4.2"
|
||||||
},
|
},
|
||||||
"gitHead": "898bc9b9f2f75df11ea0803b144862ba98b7511a"
|
"gitHead": "5801f34b3a40cbbd591805e401613b397bec9775"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,22 @@
|
|||||||
All notable changes to this project will be documented in this file.
|
All notable changes to this project will be documented in this file.
|
||||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||||
|
|
||||||
|
# [1.40.0](https://github.com/certd/certd/compare/v1.39.16...v1.40.0) (2026-05-14)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @certd/plugin-cert
|
||||||
|
|
||||||
|
## [1.39.16](https://github.com/certd/certd/compare/v1.39.15...v1.39.16) (2026-05-13)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @certd/plugin-cert
|
||||||
|
|
||||||
|
## [1.39.15](https://github.com/certd/certd/compare/v1.39.14...v1.39.15) (2026-05-13)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @certd/plugin-cert
|
||||||
|
|
||||||
|
## [1.39.14](https://github.com/certd/certd/compare/v1.39.13...v1.39.14) (2026-05-11)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @certd/plugin-cert
|
||||||
|
|
||||||
## [1.39.13](https://github.com/certd/certd/compare/v1.39.12...v1.39.13) (2026-05-10)
|
## [1.39.13](https://github.com/certd/certd/compare/v1.39.12...v1.39.13) (2026-05-10)
|
||||||
|
|
||||||
### Performance Improvements
|
### Performance Improvements
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "@certd/plugin-cert",
|
"name": "@certd/plugin-cert",
|
||||||
"private": false,
|
"private": false,
|
||||||
"version": "1.39.13",
|
"version": "1.40.0",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "./dist/index.js",
|
"main": "./dist/index.js",
|
||||||
"types": "./dist/index.d.ts",
|
"types": "./dist/index.d.ts",
|
||||||
@@ -18,10 +18,10 @@
|
|||||||
"compile": "tsc --skipLibCheck --watch"
|
"compile": "tsc --skipLibCheck --watch"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@certd/acme-client": "^1.39.13",
|
"@certd/acme-client": "^1.40.0",
|
||||||
"@certd/basic": "^1.39.13",
|
"@certd/basic": "^1.40.0",
|
||||||
"@certd/pipeline": "^1.39.13",
|
"@certd/pipeline": "^1.40.0",
|
||||||
"@certd/plugin-lib": "^1.39.13",
|
"@certd/plugin-lib": "^1.40.0",
|
||||||
"psl": "^1.9.0",
|
"psl": "^1.9.0",
|
||||||
"punycode.js": "^2.3.1"
|
"punycode.js": "^2.3.1"
|
||||||
},
|
},
|
||||||
@@ -41,5 +41,5 @@
|
|||||||
"tslib": "^2.8.1",
|
"tslib": "^2.8.1",
|
||||||
"typescript": "^5.4.2"
|
"typescript": "^5.4.2"
|
||||||
},
|
},
|
||||||
"gitHead": "898bc9b9f2f75df11ea0803b144862ba98b7511a"
|
"gitHead": "5801f34b3a40cbbd591805e401613b397bec9775"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,22 @@
|
|||||||
All notable changes to this project will be documented in this file.
|
All notable changes to this project will be documented in this file.
|
||||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||||
|
|
||||||
|
# [1.40.0](https://github.com/certd/certd/compare/v1.39.16...v1.40.0) (2026-05-14)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @certd/plugin-lib
|
||||||
|
|
||||||
|
## [1.39.16](https://github.com/certd/certd/compare/v1.39.15...v1.39.16) (2026-05-13)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @certd/plugin-lib
|
||||||
|
|
||||||
|
## [1.39.15](https://github.com/certd/certd/compare/v1.39.14...v1.39.15) (2026-05-13)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @certd/plugin-lib
|
||||||
|
|
||||||
|
## [1.39.14](https://github.com/certd/certd/compare/v1.39.13...v1.39.14) (2026-05-11)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @certd/plugin-lib
|
||||||
|
|
||||||
## [1.39.13](https://github.com/certd/certd/compare/v1.39.12...v1.39.13) (2026-05-10)
|
## [1.39.13](https://github.com/certd/certd/compare/v1.39.12...v1.39.13) (2026-05-10)
|
||||||
|
|
||||||
### Performance Improvements
|
### Performance Improvements
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "@certd/plugin-lib",
|
"name": "@certd/plugin-lib",
|
||||||
"private": false,
|
"private": false,
|
||||||
"version": "1.39.13",
|
"version": "1.40.0",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "./dist/index.js",
|
"main": "./dist/index.js",
|
||||||
"types": "./dist/index.d.ts",
|
"types": "./dist/index.d.ts",
|
||||||
@@ -23,10 +23,10 @@
|
|||||||
"@alicloud/pop-core": "^1.7.10",
|
"@alicloud/pop-core": "^1.7.10",
|
||||||
"@alicloud/tea-util": "^1.4.11",
|
"@alicloud/tea-util": "^1.4.11",
|
||||||
"@aws-sdk/client-s3": "^3.964.0",
|
"@aws-sdk/client-s3": "^3.964.0",
|
||||||
"@certd/acme-client": "^1.39.13",
|
"@certd/acme-client": "^1.40.0",
|
||||||
"@certd/basic": "^1.39.13",
|
"@certd/basic": "^1.40.0",
|
||||||
"@certd/pipeline": "^1.39.13",
|
"@certd/pipeline": "^1.40.0",
|
||||||
"@certd/plus-core": "^1.39.13",
|
"@certd/plus-core": "^1.40.0",
|
||||||
"@kubernetes/client-node": "0.21.0",
|
"@kubernetes/client-node": "0.21.0",
|
||||||
"ali-oss": "^6.22.0",
|
"ali-oss": "^6.22.0",
|
||||||
"basic-ftp": "^5.0.5",
|
"basic-ftp": "^5.0.5",
|
||||||
@@ -61,5 +61,5 @@
|
|||||||
"tslib": "^2.8.1",
|
"tslib": "^2.8.1",
|
||||||
"typescript": "^5.4.2"
|
"typescript": "^5.4.2"
|
||||||
},
|
},
|
||||||
"gitHead": "898bc9b9f2f75df11ea0803b144862ba98b7511a"
|
"gitHead": "5801f34b3a40cbbd591805e401613b397bec9775"
|
||||||
}
|
}
|
||||||
|
|||||||
+10
-5
@@ -1,4 +1,4 @@
|
|||||||
FROM node:22-alpine AS builder
|
FROM node:22-alpine3.21 AS builder
|
||||||
|
|
||||||
# RUN apk add build-base
|
# 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 && \
|
# RUN wget -O - https://github.com/jemalloc/jemalloc/releases/download/5.3.0/jemalloc-5.3.0.tar.bz2 | tar -xj && \
|
||||||
@@ -10,17 +10,22 @@ FROM node:22-alpine AS builder
|
|||||||
|
|
||||||
WORKDIR /workspace/
|
WORKDIR /workspace/
|
||||||
COPY . /workspace/
|
COPY . /workspace/
|
||||||
# armv7 目前只能用node18, pnpm9不支持node18,所以pnpm只能用8.15.7版本
|
# pnpm v11打包会报错([ERR_PNPM_IGNORED_BUILDS] Ignored build scripts),暂时固定10.33.4版本。
|
||||||
# https://github.com/nodejs/docker-node/issues/1946
|
# https://pnpm.io/zh/migration
|
||||||
RUN npm install -g pnpm
|
RUN npm install -g pnpm@10.33.4
|
||||||
|
|
||||||
#RUN cd /workspace/certd-client && pnpm install && npm run build
|
#RUN cd /workspace/certd-client && pnpm install && npm run build
|
||||||
RUN cp /workspace/certd-client/dist/* /workspace/certd-server/public/ -rf
|
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 && 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 7001
|
||||||
EXPOSE 7002
|
EXPOSE 7002
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,40 @@
|
|||||||
All notable changes to this project will be documented in this file.
|
All notable changes to this project will be documented in this file.
|
||||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||||
|
|
||||||
|
# [1.40.0](https://github.com/certd/certd/compare/v1.39.16...v1.40.0) (2026-05-14)
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* 修复第三方登录丢失state时无法在用户信息页面绑定第三方账号的bug ([45dedf5](https://github.com/certd/certd/commit/45dedf5bc779fea852e1f33dda4f31db2765633c))
|
||||||
|
* 修复群晖授权没有显示设备id输入框的bug ([2f172b5](https://github.com/certd/certd/commit/2f172b56e9411303ca15138d827bdb9bafdae4d1))
|
||||||
|
* 修复自动注册后没有跳转到控制台的bug ([4681ec9](https://github.com/certd/certd/commit/4681ec90088a3eb665427b2ac4047ec5ccefd7b3))
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* 彩虹登录支持选择多种登录方式 ([7aa0c7e](https://github.com/certd/certd/commit/7aa0c7e491fe660abb62e68792ff5474f19bd5b8))
|
||||||
|
|
||||||
|
### Performance Improvements
|
||||||
|
|
||||||
|
* 头像增加缓存时间 ([7015b1b](https://github.com/certd/certd/commit/7015b1b232602e5168a3eb8bee6d7f1776ae1e74))
|
||||||
|
|
||||||
|
## [1.39.16](https://github.com/certd/certd/compare/v1.39.15...v1.39.16) (2026-05-13)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @certd/ui-client
|
||||||
|
|
||||||
|
## [1.39.15](https://github.com/certd/certd/compare/v1.39.14...v1.39.15) (2026-05-13)
|
||||||
|
|
||||||
|
### Performance Improvements
|
||||||
|
|
||||||
|
* icon选择器增加一套logo集 ([fdd5848](https://github.com/certd/certd/commit/fdd5848df4055a6ee07dc5eabaaf6b718672882d))
|
||||||
|
* **monitor/site:** 新增站点监控页面禁用启用、检查状态两个筛选条件 ([118c15d](https://github.com/certd/certd/commit/118c15d04633a6ef06f2d9e7a7849d20f596e02c))
|
||||||
|
* **network:** 新增全局公共http请求 headers设置 ([aad9045](https://github.com/certd/certd/commit/aad9045de55e76cb2ad09cac74a7bd60a4b47124))
|
||||||
|
|
||||||
|
## [1.39.14](https://github.com/certd/certd/compare/v1.39.13...v1.39.14) (2026-05-11)
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* 修复阿里云订阅流水线创建对话框无法获取阿里订单列表的bug ([a362860](https://github.com/certd/certd/commit/a362860137bfb7072893c844fe775edc46070ee1))
|
||||||
|
|
||||||
## [1.39.13](https://github.com/certd/certd/compare/v1.39.12...v1.39.13) (2026-05-10)
|
## [1.39.13](https://github.com/certd/certd/compare/v1.39.12...v1.39.13) (2026-05-10)
|
||||||
|
|
||||||
### Bug Fixes
|
### Bug Fixes
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@certd/ui-client",
|
"name": "@certd/ui-client",
|
||||||
"version": "1.39.13",
|
"version": "1.40.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite --open",
|
"dev": "vite --open",
|
||||||
@@ -34,11 +34,11 @@
|
|||||||
"@aws-sdk/s3-request-presigner": "^3.964.0",
|
"@aws-sdk/s3-request-presigner": "^3.964.0",
|
||||||
"@certd/vue-js-cron-light": "^4.0.14",
|
"@certd/vue-js-cron-light": "^4.0.14",
|
||||||
"@ctrl/tinycolor": "^4.1.0",
|
"@ctrl/tinycolor": "^4.1.0",
|
||||||
"@fast-crud/editor-code": "^1.27.8",
|
"@fast-crud/editor-code": "^1.28.1",
|
||||||
"@fast-crud/fast-crud": "^1.27.8",
|
"@fast-crud/fast-crud": "^1.28.1",
|
||||||
"@fast-crud/fast-extends": "^1.27.8",
|
"@fast-crud/fast-extends": "^1.28.1",
|
||||||
"@fast-crud/ui-antdv4": "^1.27.8",
|
"@fast-crud/ui-antdv4": "^1.28.1",
|
||||||
"@fast-crud/ui-interface": "^1.27.8",
|
"@fast-crud/ui-interface": "^1.28.1",
|
||||||
"@iconify/tailwind": "^1.2.0",
|
"@iconify/tailwind": "^1.2.0",
|
||||||
"@iconify/vue": "^4.1.1",
|
"@iconify/vue": "^4.1.1",
|
||||||
"@manypkg/get-packages": "^2.2.2",
|
"@manypkg/get-packages": "^2.2.2",
|
||||||
@@ -106,8 +106,8 @@
|
|||||||
"zod-defaults": "^0.1.3"
|
"zod-defaults": "^0.1.3"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@certd/lib-iframe": "^1.39.13",
|
"@certd/lib-iframe": "^1.40.0",
|
||||||
"@certd/pipeline": "^1.39.13",
|
"@certd/pipeline": "^1.40.0",
|
||||||
"@rollup/plugin-commonjs": "^25.0.7",
|
"@rollup/plugin-commonjs": "^25.0.7",
|
||||||
"@rollup/plugin-node-resolve": "^15.2.3",
|
"@rollup/plugin-node-resolve": "^15.2.3",
|
||||||
"@types/chai": "^4.3.12",
|
"@types/chai": "^4.3.12",
|
||||||
|
|||||||
@@ -74,7 +74,6 @@ const props = defineProps<
|
|||||||
uploadCert?: UploadCertProps;
|
uploadCert?: UploadCertProps;
|
||||||
} & ComponentPropsType
|
} & ComponentPropsType
|
||||||
>();
|
>();
|
||||||
debugger;
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
"update:value": any;
|
"update:value": any;
|
||||||
}>();
|
}>();
|
||||||
@@ -141,8 +140,7 @@ const getOptions = async () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
message.value = "获取中...";
|
||||||
message.value = "";
|
|
||||||
hasError.value = false;
|
hasError.value = false;
|
||||||
loading.value = true;
|
loading.value = true;
|
||||||
const pageNo = pagerRef.value.pageNo;
|
const pageNo = pagerRef.value.pageNo;
|
||||||
|
|||||||
@@ -78,8 +78,13 @@ export default {
|
|||||||
passkeyRegisterFailed: "Passkey registration failed",
|
passkeyRegisterFailed: "Passkey registration failed",
|
||||||
title: "Change Password",
|
title: "Change Password",
|
||||||
weakPasswordWarning: "For your account security, please change your password immediately",
|
weakPasswordWarning: "For your account security, please change your password immediately",
|
||||||
|
initPasswordWarning: "This account does not have a login password yet. Please set one first",
|
||||||
|
initPasswordTitle: "Set Password",
|
||||||
changeNow: "Change Now",
|
changeNow: "Change Now",
|
||||||
|
setNow: "Set Now",
|
||||||
|
notNow: "Not Now",
|
||||||
successMessage: "Changed successfully",
|
successMessage: "Changed successfully",
|
||||||
|
initPasswordSuccessMessage: "Set successfully",
|
||||||
oldPassword: "Old Password",
|
oldPassword: "Old Password",
|
||||||
oldPasswordRequired: "Please enter the old password",
|
oldPasswordRequired: "Please enter the old password",
|
||||||
newPassword: "New Password",
|
newPassword: "New Password",
|
||||||
|
|||||||
@@ -91,6 +91,8 @@ export default {
|
|||||||
reverseProxyEmpty: "No reverse proxy list configured",
|
reverseProxyEmpty: "No reverse proxy list configured",
|
||||||
environmentVars: "Environment Variables",
|
environmentVars: "Environment Variables",
|
||||||
environmentVarsHelper: "configure the runtime environment variables, one per line, format: KEY=VALUE",
|
environmentVarsHelper: "configure the runtime environment variables, one per line, format: KEY=VALUE",
|
||||||
|
commonHeaders: "Common Headers",
|
||||||
|
commonHeadersHelper: "Common headers automatically added to server-side HTTP requests, one per line, format: KEY=VALUE. Existing request headers with the same name are not overwritten.",
|
||||||
|
|
||||||
bindUrl: "Bind URL",
|
bindUrl: "Bind URL",
|
||||||
bindUrlHelper: "Bind URL, used as your site URL in notifications",
|
bindUrlHelper: "Bind URL, used as your site URL in notifications",
|
||||||
|
|||||||
@@ -79,8 +79,13 @@ export default {
|
|||||||
|
|
||||||
title: "修改密码",
|
title: "修改密码",
|
||||||
weakPasswordWarning: "为了您的账户安全,请立即修改密码",
|
weakPasswordWarning: "为了您的账户安全,请立即修改密码",
|
||||||
|
initPasswordWarning: "当前账号还未设置登录密码,请先设置密码",
|
||||||
|
initPasswordTitle: "设置密码",
|
||||||
changeNow: "立即修改",
|
changeNow: "立即修改",
|
||||||
|
setNow: "立即设置",
|
||||||
|
notNow: "暂不设置",
|
||||||
successMessage: "修改成功",
|
successMessage: "修改成功",
|
||||||
|
initPasswordSuccessMessage: "设置成功",
|
||||||
oldPassword: "旧密码",
|
oldPassword: "旧密码",
|
||||||
oldPasswordRequired: "请输入旧密码",
|
oldPasswordRequired: "请输入旧密码",
|
||||||
newPassword: "新密码",
|
newPassword: "新密码",
|
||||||
|
|||||||
@@ -89,6 +89,8 @@ export default {
|
|||||||
reverseProxyEmpty: "未配置反向代理",
|
reverseProxyEmpty: "未配置反向代理",
|
||||||
environmentVars: "环境变量",
|
environmentVars: "环境变量",
|
||||||
environmentVarsHelper: "配置运行时环境变量,每行一个,格式:KEY=VALUE",
|
environmentVarsHelper: "配置运行时环境变量,每行一个,格式:KEY=VALUE",
|
||||||
|
commonHeaders: "公共请求头",
|
||||||
|
commonHeadersHelper: "服务端发起 HTTP 请求时自动附加的公共请求头,每行一个,格式:KEY=VALUE;请求中已设置同名 Header 时不会覆盖\n注意: 不要将token等敏感内容放在此处,仅限个人和公司内部使用,商业版不要设置",
|
||||||
bindUrl: "绑定URL",
|
bindUrl: "绑定URL",
|
||||||
bindUrlHelper: "绑定URL,在各类通知中显示你的站点URL",
|
bindUrlHelper: "绑定URL,在各类通知中显示你的站点URL",
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -85,6 +85,7 @@ export type SysPublicSetting = {
|
|||||||
type: string;
|
type: string;
|
||||||
title: string;
|
title: string;
|
||||||
addonId: number;
|
addonId: number;
|
||||||
|
icon?: string;
|
||||||
}
|
}
|
||||||
>;
|
>;
|
||||||
// 系统通知
|
// 系统通知
|
||||||
@@ -99,6 +100,7 @@ export type SuiteSetting = {
|
|||||||
export type SysPrivateSetting = {
|
export type SysPrivateSetting = {
|
||||||
httpProxy?: string;
|
httpProxy?: string;
|
||||||
httpsProxy?: string;
|
httpsProxy?: string;
|
||||||
|
commonHeaders?: string;
|
||||||
reverseProxies?: any;
|
reverseProxies?: any;
|
||||||
dnsResultOrder?: string;
|
dnsResultOrder?: string;
|
||||||
commonCnameEnabled?: boolean;
|
commonCnameEnabled?: boolean;
|
||||||
|
|||||||
@@ -38,6 +38,7 @@ export interface UserInfoRes {
|
|||||||
avatar?: string;
|
avatar?: string;
|
||||||
roleIds: number[];
|
roleIds: number[];
|
||||||
isWeak?: boolean;
|
isWeak?: boolean;
|
||||||
|
needInitPassword?: boolean;
|
||||||
validTime?: number;
|
validTime?: number;
|
||||||
status?: number;
|
status?: number;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -38,6 +38,9 @@ export const useUserStore = defineStore({
|
|||||||
getToken(): string {
|
getToken(): string {
|
||||||
return this.token || LocalStorage.get(TOKEN_KEY);
|
return this.token || LocalStorage.get(TOKEN_KEY);
|
||||||
},
|
},
|
||||||
|
isLogined(): boolean {
|
||||||
|
return !!this.getToken;
|
||||||
|
},
|
||||||
isAdmin(): boolean {
|
isAdmin(): boolean {
|
||||||
return this.getUserInfo.roleIds?.includes(1) || this.getUserInfo.id === 1;
|
return this.getUserInfo.roleIds?.includes(1) || this.getUserInfo.id === 1;
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -11,7 +11,18 @@ export type MergeScriptContext = {
|
|||||||
export function useReference(formItem: any) {
|
export function useReference(formItem: any) {
|
||||||
if (formItem.mergeScript) {
|
if (formItem.mergeScript) {
|
||||||
const ctx = {
|
const ctx = {
|
||||||
compute,
|
compute: (opts: any) => {
|
||||||
|
const func = (context: any) => {
|
||||||
|
debugger;
|
||||||
|
let form = context.form || {};
|
||||||
|
form = form.input || form.body || form; // form.access去掉,历史原因,access的mergeScript会处理form.access
|
||||||
|
return opts({
|
||||||
|
...context,
|
||||||
|
form,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
return compute(func);
|
||||||
|
},
|
||||||
asyncCompute,
|
asyncCompute,
|
||||||
computed,
|
computed,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -15,6 +15,14 @@ export async function changePassword(form: any) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function initPassword(form: any) {
|
||||||
|
return await request({
|
||||||
|
url: "/mine/initPassword",
|
||||||
|
method: "POST",
|
||||||
|
data: form,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
export async function UpdateProfile(form: any) {
|
export async function UpdateProfile(form: any) {
|
||||||
return await request({
|
return await request({
|
||||||
url: "/mine/updateProfile",
|
url: "/mine/updateProfile",
|
||||||
@@ -68,20 +76,23 @@ export async function GetOauthProviders() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function UnbindOauth(type: string) {
|
export async function UnbindOauth(type: string, subtype?: string) {
|
||||||
return await request({
|
return await request({
|
||||||
url: "/oauth/unbind",
|
url: "/oauth/unbind",
|
||||||
method: "POST",
|
method: "POST",
|
||||||
data: { type },
|
data: {
|
||||||
|
type: subtype ? `${type}:${subtype}` : type,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function OauthBoundUrl(type: string) {
|
export async function OauthBoundUrl(type: string, subtype?: string) {
|
||||||
return await request({
|
return await request({
|
||||||
url: "/oauth/login",
|
url: "/oauth/login",
|
||||||
method: "POST",
|
method: "POST",
|
||||||
data: {
|
data: {
|
||||||
type,
|
type,
|
||||||
|
subtype,
|
||||||
forType: "bind",
|
forType: "bind",
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import { ref } from "vue";
|
|||||||
import { useI18n } from "/src/locales";
|
import { useI18n } from "/src/locales";
|
||||||
|
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
import { CrudOptions, useColumns, useFormWrapper } from "@fast-crud/fast-crud";
|
import { compute, CrudOptions, useColumns, useFormWrapper } from "@fast-crud/fast-crud";
|
||||||
import * as api from "/@/views/certd/mine/api";
|
import * as api from "/@/views/certd/mine/api";
|
||||||
import { notification } from "ant-design-vue";
|
import { notification } from "ant-design-vue";
|
||||||
import { useUserStore } from "/@/store/user";
|
import { useUserStore } from "/@/store/user";
|
||||||
@@ -20,6 +20,11 @@ defineProps<{
|
|||||||
|
|
||||||
let passwordFormRef = ref();
|
let passwordFormRef = ref();
|
||||||
|
|
||||||
|
type OpenOptions = {
|
||||||
|
password?: string;
|
||||||
|
init?: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
const validatePass1 = async (rule: any, value: any) => {
|
const validatePass1 = async (rule: any, value: any) => {
|
||||||
if (value === "") {
|
if (value === "") {
|
||||||
throw new Error(t("authentication.enterPassword"));
|
throw new Error(t("authentication.enterPassword"));
|
||||||
@@ -53,19 +58,33 @@ const passwordFormOptions: CrudOptions = {
|
|||||||
width: "500px",
|
width: "500px",
|
||||||
},
|
},
|
||||||
async doSubmit({ form }) {
|
async doSubmit({ form }) {
|
||||||
|
if (form.init) {
|
||||||
|
await api.initPassword(form);
|
||||||
|
} else {
|
||||||
await api.changePassword(form);
|
await api.changePassword(form);
|
||||||
|
}
|
||||||
//重新加载用户信息
|
//重新加载用户信息
|
||||||
await userStore.loadUserInfo();
|
await userStore.loadUserInfo();
|
||||||
},
|
},
|
||||||
async afterSubmit() {
|
async afterSubmit() {
|
||||||
notification.success({ message: t("authentication.successMessage") });
|
const formData = passwordFormRef.value?.getFormData?.();
|
||||||
|
const message = formData?.init ? t("authentication.initPasswordSuccessMessage") : t("authentication.successMessage");
|
||||||
|
notification.success({ message });
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
columns: {
|
columns: {
|
||||||
|
init: {
|
||||||
|
title: "init",
|
||||||
|
type: "text",
|
||||||
|
form: {
|
||||||
|
show: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
password: {
|
password: {
|
||||||
title: t("authentication.oldPassword"),
|
title: t("authentication.oldPassword"),
|
||||||
type: "password",
|
type: "password",
|
||||||
form: {
|
form: {
|
||||||
|
show: compute(({ form }) => form.init !== true),
|
||||||
rules: [{ required: true, message: t("authentication.oldPasswordRequired") }],
|
rules: [{ required: true, message: t("authentication.oldPasswordRequired") }],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -97,12 +116,16 @@ const passwordFormOptions: CrudOptions = {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
async function open(opts: { password: "" }) {
|
async function open(opts: OpenOptions = {}) {
|
||||||
const formOptions = buildFormOptions(passwordFormOptions);
|
const formOptions = buildFormOptions(passwordFormOptions);
|
||||||
formOptions.newInstance = true; //新实例打开
|
formOptions.newInstance = true; //新实例打开
|
||||||
|
if (opts.init) {
|
||||||
|
formOptions.wrapper.title = t("authentication.initPasswordTitle");
|
||||||
|
}
|
||||||
passwordFormRef.value = await openDialog(formOptions);
|
passwordFormRef.value = await openDialog(formOptions);
|
||||||
passwordFormRef.value.setFormData({
|
passwordFormRef.value.setFormData({
|
||||||
password: opts.password,
|
init: opts.init === true,
|
||||||
|
password: opts.password || "",
|
||||||
});
|
});
|
||||||
console.log(passwordFormRef.value);
|
console.log(passwordFormRef.value);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -48,7 +48,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="action-buttons gap-2">
|
<div class="action-buttons gap-2">
|
||||||
<change-password-button :show-button="true" />
|
<change-password-button ref="changePasswordButtonRef" :show-button="true" />
|
||||||
|
|
||||||
<a-button type="primary" class="action-btn" @click="goSecuritySetting">
|
<a-button type="primary" class="action-btn" @click="goSecuritySetting">
|
||||||
{{ t("authentication.securitySettingTip") }}
|
{{ t("authentication.securitySettingTip") }}
|
||||||
@@ -78,11 +78,11 @@
|
|||||||
<a-tag v-else color="red" class="bound-tag1">未绑定</a-tag>
|
<a-tag v-else color="red" class="bound-tag1">未绑定</a-tag>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<a-button v-if="item.bound" type="primary" danger class="action-btn" @click="unbind(item.name)">
|
<a-button v-if="item.bound" type="primary" danger class="action-btn" @click="unbind(item)">
|
||||||
<template #icon><fs-icon icon="ion:unlink-outline" /></template>
|
<template #icon><fs-icon icon="ion:unlink-outline" /></template>
|
||||||
解绑
|
解绑
|
||||||
</a-button>
|
</a-button>
|
||||||
<a-button v-else type="primary" class="action-btn" @click="bind(item.name)">
|
<a-button v-else type="primary" class="action-btn" @click="bind(item)">
|
||||||
<template #icon><fs-icon icon="ion:link-outline" /></template>
|
<template #icon><fs-icon icon="ion:link-outline" /></template>
|
||||||
绑定
|
绑定
|
||||||
</a-button>
|
</a-button>
|
||||||
@@ -214,7 +214,7 @@ async function loadOauthProviders() {
|
|||||||
|
|
||||||
const computedOauthBounds = computed(() => {
|
const computedOauthBounds = computed(() => {
|
||||||
const list = oauthProviders.value.map(item => {
|
const list = oauthProviders.value.map(item => {
|
||||||
const bound = oauthBounds.value.find(bound => bound.type === item.name);
|
const bound = oauthBounds.value.find(bound => bound.type === buildOauthBoundType(item));
|
||||||
return {
|
return {
|
||||||
...item,
|
...item,
|
||||||
bound,
|
bound,
|
||||||
@@ -223,20 +223,24 @@ const computedOauthBounds = computed(() => {
|
|||||||
return list;
|
return list;
|
||||||
});
|
});
|
||||||
|
|
||||||
async function unbind(type: string) {
|
function buildOauthBoundType(item: any) {
|
||||||
|
return item.subtype ? `${item.name}:${item.subtype}` : item.name;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function unbind(item: any) {
|
||||||
Modal.confirm({
|
Modal.confirm({
|
||||||
title: "确认解绑吗?",
|
title: "确认解绑吗?",
|
||||||
okText: "确认",
|
okText: "确认",
|
||||||
okType: "danger",
|
okType: "danger",
|
||||||
onOk: async () => {
|
onOk: async () => {
|
||||||
await api.UnbindOauth(type);
|
await api.UnbindOauth(item.name, item.subtype);
|
||||||
await loadOauthBounds();
|
await loadOauthBounds();
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async function bind(type: string) {
|
async function bind(item: any) {
|
||||||
const res = await api.OauthBoundUrl(type);
|
const res = await api.OauthBoundUrl(item.name, item.subtype);
|
||||||
const loginUrl = res.loginUrl;
|
const loginUrl = res.loginUrl;
|
||||||
window.location.href = loginUrl;
|
window.location.href = loginUrl;
|
||||||
}
|
}
|
||||||
@@ -383,6 +387,7 @@ const checkPasskeySupport = () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
const userStore = useUserStore();
|
const userStore = useUserStore();
|
||||||
|
const changePasswordButtonRef = ref();
|
||||||
const userAvatar = computed(() => {
|
const userAvatar = computed(() => {
|
||||||
if (isEmpty(userInfo.value.avatar)) {
|
if (isEmpty(userInfo.value.avatar)) {
|
||||||
return "";
|
return "";
|
||||||
@@ -391,11 +396,26 @@ const userAvatar = computed(() => {
|
|||||||
return userInfo.value.avatar;
|
return userInfo.value.avatar;
|
||||||
}
|
}
|
||||||
|
|
||||||
return `api/basic/file/download?token=${userStore.getToken}&key=${userInfo.value.avatar}`;
|
return `api/basic/file/download?key=${userInfo.value.avatar}`;
|
||||||
});
|
});
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
await getUserInfo();
|
await getUserInfo();
|
||||||
|
userStore.setUserInfo(userInfo.value);
|
||||||
|
if (userInfo.value.needInitPassword === true) {
|
||||||
|
Modal.confirm({
|
||||||
|
title: t("authentication.initPasswordTitle"),
|
||||||
|
content: t("authentication.initPasswordWarning"),
|
||||||
|
okText: t("authentication.setNow"),
|
||||||
|
cancelText: t("authentication.notNow"),
|
||||||
|
closable: true,
|
||||||
|
onOk: () => {
|
||||||
|
changePasswordButtonRef.value.open({
|
||||||
|
init: true,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
await loadContactCapability();
|
await loadContactCapability();
|
||||||
await loadOauthBounds();
|
await loadOauthBounds();
|
||||||
await loadOauthProviders();
|
await loadOauthProviders();
|
||||||
|
|||||||
@@ -320,6 +320,9 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
|
|||||||
title: t("monitor.siteName"),
|
title: t("monitor.siteName"),
|
||||||
search: {
|
search: {
|
||||||
show: true,
|
show: true,
|
||||||
|
col: {
|
||||||
|
span: 3,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
type: "text",
|
type: "text",
|
||||||
form: {
|
form: {
|
||||||
@@ -333,6 +336,9 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
|
|||||||
title: t("monitor.domain"),
|
title: t("monitor.domain"),
|
||||||
search: {
|
search: {
|
||||||
show: true,
|
show: true,
|
||||||
|
col: {
|
||||||
|
span: 3,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
type: "text",
|
type: "text",
|
||||||
form: {
|
form: {
|
||||||
@@ -411,6 +417,9 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
|
|||||||
title: t("monitor.certDomains"),
|
title: t("monitor.certDomains"),
|
||||||
search: {
|
search: {
|
||||||
show: true,
|
show: true,
|
||||||
|
col: {
|
||||||
|
span: 3,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
type: "text",
|
type: "text",
|
||||||
form: {
|
form: {
|
||||||
@@ -451,6 +460,9 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
|
|||||||
title: t("monitor.certStatus"),
|
title: t("monitor.certStatus"),
|
||||||
search: {
|
search: {
|
||||||
show: true,
|
show: true,
|
||||||
|
col: {
|
||||||
|
span: 2,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
type: "dict-select",
|
type: "dict-select",
|
||||||
dict: dict({
|
dict: dict({
|
||||||
@@ -472,7 +484,10 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
|
|||||||
checkStatus: {
|
checkStatus: {
|
||||||
title: t("monitor.checkStatus"),
|
title: t("monitor.checkStatus"),
|
||||||
search: {
|
search: {
|
||||||
show: false,
|
show: true,
|
||||||
|
col: {
|
||||||
|
span: 2,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
type: "dict-select",
|
type: "dict-select",
|
||||||
dict: checkStatusDict,
|
dict: checkStatusDict,
|
||||||
@@ -578,6 +593,9 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
|
|||||||
type: "dict-select",
|
type: "dict-select",
|
||||||
search: {
|
search: {
|
||||||
show: true,
|
show: true,
|
||||||
|
col: {
|
||||||
|
span: 3,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
dict: groupDictRef,
|
dict: groupDictRef,
|
||||||
form: {
|
form: {
|
||||||
@@ -631,7 +649,10 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
|
|||||||
disabled: {
|
disabled: {
|
||||||
title: t("monitor.disabled"),
|
title: t("monitor.disabled"),
|
||||||
search: {
|
search: {
|
||||||
show: false,
|
show: true,
|
||||||
|
col: {
|
||||||
|
span: 2,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
type: "dict-switch",
|
type: "dict-switch",
|
||||||
dict: dict({
|
dict: dict({
|
||||||
|
|||||||
@@ -94,9 +94,15 @@ export function setRunnableIds(pipeline: any) {
|
|||||||
return JSON.parse(content);
|
return JSON.parse(content);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useCertPipelineCreator() {
|
export function useCertPipelineCreator({ formWrapperRef }: { formWrapperRef: Ref<any> }) {
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
const { openCrudFormDialog } = useFormWrapper();
|
|
||||||
|
function open(opts: any) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
formWrapperRef.value.open(opts);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
const { openCrudFormDialog } = useFormWrapper({ open });
|
||||||
|
|
||||||
const pluginStore = usePluginStore();
|
const pluginStore = usePluginStore();
|
||||||
const settingStore = useSettingStore();
|
const settingStore = useSettingStore();
|
||||||
@@ -111,10 +117,10 @@ export function useCertPipelineCreator() {
|
|||||||
// inputs[inputKey].form.show = true;
|
// inputs[inputKey].form.show = true;
|
||||||
const inputDefine = cloneDeep(certPlugin.input[inputKey]);
|
const inputDefine = cloneDeep(certPlugin.input[inputKey]);
|
||||||
if (inputDefine.maybeNeed) {
|
if (inputDefine.maybeNeed) {
|
||||||
moreParams.push(inputKey);
|
moreParams.push("input." + inputKey);
|
||||||
}
|
}
|
||||||
useReference(inputDefine);
|
useReference(inputDefine);
|
||||||
inputs[inputKey] = {
|
inputs["input." + inputKey] = {
|
||||||
title: inputDefine.title,
|
title: inputDefine.title,
|
||||||
form: {
|
form: {
|
||||||
...inputDefine,
|
...inputDefine,
|
||||||
@@ -135,17 +141,19 @@ export function useCertPipelineCreator() {
|
|||||||
const DEFAULT_RENEW_DAYS = settingStore.sysPublic.defaultCertRenewDays || settingStore.sysPublic.defaultWillExpireDays || 20;
|
const DEFAULT_RENEW_DAYS = settingStore.sysPublic.defaultCertRenewDays || settingStore.sysPublic.defaultWillExpireDays || 20;
|
||||||
|
|
||||||
merge(inputs, {
|
merge(inputs, {
|
||||||
renewDays: {
|
"input.renewDays": {
|
||||||
form: {
|
form: {
|
||||||
value: DEFAULT_RENEW_DAYS,
|
value: DEFAULT_RENEW_DAYS,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const initialForm = req.initialForm || {};
|
||||||
|
initialForm.type = certPlugin.name;
|
||||||
return {
|
return {
|
||||||
crudOptions: {
|
crudOptions: {
|
||||||
form: {
|
form: {
|
||||||
initialForm: req.initialForm || {},
|
initialForm: initialForm,
|
||||||
doSubmit,
|
doSubmit,
|
||||||
wrapper: {
|
wrapper: {
|
||||||
wrapClassName: "cert_pipeline_create_form",
|
wrapClassName: "cert_pipeline_create_form",
|
||||||
@@ -164,44 +172,6 @@ export function useCertPipelineCreator() {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
columns: {
|
columns: {
|
||||||
// certApplyPlugin: {
|
|
||||||
// title: t("certd.plugin.selectTitle"),
|
|
||||||
// type: "dict-select",
|
|
||||||
// dict: dict({
|
|
||||||
// data: [
|
|
||||||
// { value: "CertApply", label: "JS-ACME" },
|
|
||||||
// { value: "CertApplyLego", label: "Lego-ACME" },
|
|
||||||
// { value: "CertApplyGetFormAliyun", label: "Aliyun-Order" },
|
|
||||||
// ],
|
|
||||||
// }),
|
|
||||||
// form: {
|
|
||||||
// order: 0,
|
|
||||||
// value: "CertApply",
|
|
||||||
// helper: {
|
|
||||||
// render: () => {
|
|
||||||
// return (
|
|
||||||
// <ul>
|
|
||||||
// <li>{t("certd.plugin.jsAcme")}</li>
|
|
||||||
// <li>{t("certd.plugin.legoAcme")}</li>
|
|
||||||
// <li>{t("certd.plugin.aliyunOrder")}</li>
|
|
||||||
// </ul>
|
|
||||||
// );
|
|
||||||
// },
|
|
||||||
// },
|
|
||||||
// valueChange: {
|
|
||||||
// handle: async ({ form, value }) => {
|
|
||||||
// const config = await pluginStore.getPluginConfig({
|
|
||||||
// name: value,
|
|
||||||
// type: "builtIn",
|
|
||||||
// });
|
|
||||||
// if (config.sysSetting?.input) {
|
|
||||||
// merge(form, config.sysSetting.input);
|
|
||||||
// }
|
|
||||||
// },
|
|
||||||
// immediate: true,
|
|
||||||
// },
|
|
||||||
// },
|
|
||||||
// },
|
|
||||||
...inputs,
|
...inputs,
|
||||||
triggerCron: {
|
triggerCron: {
|
||||||
title: t("certd.pipelineForm.triggerCronTitle"),
|
title: t("certd.pipelineForm.triggerCronTitle"),
|
||||||
@@ -346,20 +316,20 @@ export function useCertPipelineCreator() {
|
|||||||
await checkPipelineLimit();
|
await checkPipelineLimit();
|
||||||
|
|
||||||
//设置系统初始值
|
//设置系统初始值
|
||||||
const initialForm: any = {};
|
const initialForm: any = { input: {} };
|
||||||
const pluginSysConfig = await pluginStore.getPluginConfig({ name: req.pluginName, type: "builtIn" });
|
const pluginSysConfig = await pluginStore.getPluginConfig({ name: req.pluginName, type: "builtIn" });
|
||||||
if (pluginSysConfig.sysSetting?.input) {
|
if (pluginSysConfig.sysSetting?.input) {
|
||||||
for (const key in pluginSysConfig.sysSetting?.input) {
|
for (const key in pluginSysConfig.sysSetting?.input) {
|
||||||
initialForm[key] = pluginSysConfig.sysSetting?.input[key];
|
initialForm.input[key] = pluginSysConfig.sysSetting?.input[key];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function doSubmit({ form }: any) {
|
async function doSubmit({ form }: any) {
|
||||||
// const certDetail = readCertDetail(form.cert.crt);
|
// const certDetail = readCertDetail(form.cert.crt);
|
||||||
// 添加certd pipeline
|
// 添加certd pipeline
|
||||||
const pluginInput = omit(form, ["triggerCron", "notification", "notificationTarget", "notificationWhen", "certApplyPlugin", "groupId"]);
|
const pluginInput = form.input;
|
||||||
let pipeline: any = {
|
let pipeline: any = {
|
||||||
title: form.domains[0] + "证书自动化",
|
title: pluginInput.domains[0] + "证书自动化",
|
||||||
runnableType: "pipeline",
|
runnableType: "pipeline",
|
||||||
stages: [
|
stages: [
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -50,6 +50,7 @@
|
|||||||
<div>{{ t("certd.applyCertificate") }}</div>
|
<div>{{ t("certd.applyCertificate") }}</div>
|
||||||
</template>
|
</template>
|
||||||
</fs-crud>
|
</fs-crud>
|
||||||
|
<fs-form-wrapper ref="formWrapperRef"></fs-form-wrapper>
|
||||||
</fs-page>
|
</fs-page>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -90,10 +91,9 @@ function onActionbarMoreItemClick(req: { key: string; item: any }) {
|
|||||||
openCertApplyDialog({ key: req.key, title: req.item?.title });
|
openCertApplyDialog({ key: req.key, title: req.item?.title });
|
||||||
}
|
}
|
||||||
|
|
||||||
const certdFormRef = ref<typeof CertdForm>();
|
|
||||||
const currentPluginRef = ref();
|
const currentPluginRef = ref();
|
||||||
provide("getCurrentPluginDefine", () => {
|
provide("getCurrentPluginDefine", () => {
|
||||||
return currentPluginRef.value;
|
return currentPluginRef;
|
||||||
});
|
});
|
||||||
|
|
||||||
const addMorePipelineBtns = computed(() => {
|
const addMorePipelineBtns = computed(() => {
|
||||||
@@ -104,7 +104,9 @@ const addMorePipelineBtns = computed(() => {
|
|||||||
{ key: "BatchAddPipeline", title: t("certd.pipelinePage.batchAddPipeline"), icon: "ion:duplicate" },
|
{ key: "BatchAddPipeline", title: t("certd.pipelinePage.batchAddPipeline"), icon: "ion:duplicate" },
|
||||||
];
|
];
|
||||||
});
|
});
|
||||||
const { openAddCertdPipelineDialog } = useCertPipelineCreator();
|
|
||||||
|
const formWrapperRef = ref<any>();
|
||||||
|
const { openAddCertdPipelineDialog } = useCertPipelineCreator({ formWrapperRef });
|
||||||
function openCertApplyDialog(req: { key: string; title: string }) {
|
function openCertApplyDialog(req: { key: string; title: string }) {
|
||||||
if (req.key === "AddPipeline") {
|
if (req.key === "AddPipeline") {
|
||||||
crudExpose.openAdd({});
|
crudExpose.openAdd({});
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { request } from "/src/api/service";
|
|||||||
|
|
||||||
const apiPrefix = "/oauth";
|
const apiPrefix = "/oauth";
|
||||||
|
|
||||||
export async function OauthLogin(type: string, forType?: string, from?: string) {
|
export async function OauthLogin(type: string, forType?: string, from?: string, subtype?: string) {
|
||||||
return await request({
|
return await request({
|
||||||
url: apiPrefix + `/login`,
|
url: apiPrefix + `/login`,
|
||||||
method: "post",
|
method: "post",
|
||||||
@@ -10,6 +10,7 @@ export async function OauthLogin(type: string, forType?: string, from?: string)
|
|||||||
type,
|
type,
|
||||||
forType: forType || "login",
|
forType: forType || "login",
|
||||||
from: from || "web",
|
from: from || "web",
|
||||||
|
subtype,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,8 +9,9 @@
|
|||||||
<div>第三方({{ oauthType }})登录成功,您还未绑定账号,请选择</div>
|
<div>第三方({{ oauthType }})登录成功,您还未绑定账号,请选择</div>
|
||||||
|
|
||||||
<div class="mt-10">
|
<div class="mt-10">
|
||||||
<a-button class="w-full mt-10" type="primary" @click="goBindUser">绑定已有账号</a-button>
|
<a-button v-if="!userStore.isLogined" class="w-full mt-10" type="primary" @click="goBindUser">绑定已有账号</a-button>
|
||||||
<a-button v-if="settingStore.sysPublic.registerEnabled" class="w-full mt-10" type="primary" @click="autoRegister">创建新账号</a-button>
|
<a-button v-else class="w-full mt-10" type="primary" @click="doBindCurrent">绑定当前登录账号({{ userStore.getUserInfo.username }} - {{ userStore.getUserInfo.nickName }})</a-button>
|
||||||
|
<a-button v-if="settingStore.sysPublic.registerEnabled" class="w-full mt-10" type="primary" @click="autoRegister">创建新账号绑定</a-button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="w-full mt-10">
|
<div class="w-full mt-10">
|
||||||
@@ -48,7 +49,7 @@ async function handleOauthToken() {
|
|||||||
//登录成功
|
//登录成功
|
||||||
userStore.onLoginSuccess(res);
|
userStore.onLoginSuccess(res);
|
||||||
//跳转到首页
|
//跳转到首页
|
||||||
router.replace("/");
|
router.replace("/index");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (res.bindRequired) {
|
if (res.bindRequired) {
|
||||||
@@ -63,6 +64,15 @@ async function handleOauthToken() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function doBindCurrent() {
|
||||||
|
await api.BindUser(validationCode);
|
||||||
|
notification.success({
|
||||||
|
message: "绑定成功",
|
||||||
|
});
|
||||||
|
//跳转到首页
|
||||||
|
router.replace("/certd/mine/user-profile");
|
||||||
|
}
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
if (error.value) {
|
if (error.value) {
|
||||||
return;
|
return;
|
||||||
@@ -70,12 +80,7 @@ onMounted(async () => {
|
|||||||
|
|
||||||
if (forType === "bind") {
|
if (forType === "bind") {
|
||||||
//从用户中心页面,进行第三方账号的绑定
|
//从用户中心页面,进行第三方账号的绑定
|
||||||
await api.BindUser(validationCode);
|
await doBindCurrent();
|
||||||
notification.success({
|
|
||||||
message: "绑定成功",
|
|
||||||
});
|
|
||||||
//跳转到首页
|
|
||||||
router.replace("/certd/mine/user-profile");
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -98,7 +103,7 @@ async function autoRegister() {
|
|||||||
//登录成功
|
//登录成功
|
||||||
userStore.onLoginSuccess(res);
|
userStore.onLoginSuccess(res);
|
||||||
//跳转到首页
|
//跳转到首页
|
||||||
router.replace("/");
|
router.replace("/index");
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
<style lang="less">
|
<style lang="less">
|
||||||
|
|||||||
@@ -5,8 +5,8 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="flex justify-center items-center gap-4 flex-wrap md:flex-nowrap">
|
<div class="flex justify-center items-center gap-4 flex-wrap md:flex-nowrap">
|
||||||
<passkey-login></passkey-login>
|
<passkey-login></passkey-login>
|
||||||
<template v-for="item in oauthProviderList" :key="item.type">
|
<template v-for="item in oauthProviderList" :key="buildProviderKey(item)">
|
||||||
<div v-if="item.addonId" class="oauth-icon-button pointer" @click="goOauthLogin(item.name)">
|
<div v-if="item.addonId" class="oauth-icon-button pointer" @click="goOauthLogin(item)">
|
||||||
<div><fs-icon :icon="item.icon" class="text-blue-600 text-40" /></div>
|
<div><fs-icon :icon="item.icon" class="text-blue-600 text-40" /></div>
|
||||||
<div class="ellipsis title" :title="item.addonTitle || item.title">{{ item.addonTitle || item.title }}</div>
|
<div class="ellipsis title" :title="item.addonTitle || item.title">{{ item.addonTitle || item.title }}</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -22,7 +22,17 @@ import { useSettingStore } from "/@/store/settings";
|
|||||||
import { useRoute } from "vue-router";
|
import { useRoute } from "vue-router";
|
||||||
import PasskeyLogin from "../login/passkey-login.vue";
|
import PasskeyLogin from "../login/passkey-login.vue";
|
||||||
|
|
||||||
const oauthProviderList = ref([]);
|
type OauthProviderItem = {
|
||||||
|
name: string;
|
||||||
|
type?: string;
|
||||||
|
subtype?: string;
|
||||||
|
title: string;
|
||||||
|
addonTitle?: string;
|
||||||
|
icon: string;
|
||||||
|
addonId?: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
const oauthProviderList = ref<OauthProviderItem[]>([]);
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
oauthOnly?: boolean;
|
oauthOnly?: boolean;
|
||||||
}>();
|
}>();
|
||||||
@@ -42,15 +52,19 @@ onMounted(async () => {
|
|||||||
if (settingStore.sysPublic.oauthAutoRedirect && queryOauthOnly !== "false") {
|
if (settingStore.sysPublic.oauthAutoRedirect && queryOauthOnly !== "false") {
|
||||||
const firstOauth = oauthProviderList.value.find(item => item.addonId > 0);
|
const firstOauth = oauthProviderList.value.find(item => item.addonId > 0);
|
||||||
if (firstOauth) {
|
if (firstOauth) {
|
||||||
goOauthLogin(firstOauth.name);
|
goOauthLogin(firstOauth);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
async function goOauthLogin(type: string) {
|
function buildProviderKey(item: OauthProviderItem) {
|
||||||
|
return `${item.name}:${item.subtype || ""}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function goOauthLogin(item: OauthProviderItem) {
|
||||||
//获取第三方登录URL
|
//获取第三方登录URL
|
||||||
const from = "web";
|
const from = "web";
|
||||||
const res = await api.OauthLogin(type, from);
|
const res = await api.OauthLogin(item.name, "login", from, item.subtype);
|
||||||
const loginUrl = res.loginUrl;
|
const loginUrl = res.loginUrl;
|
||||||
window.location.href = loginUrl;
|
window.location.href = loginUrl;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -114,7 +114,7 @@ export async function GetSmsTypeDefine(type: string) {
|
|||||||
|
|
||||||
export async function GetOauthProviders() {
|
export async function GetOauthProviders() {
|
||||||
return await request({
|
return await request({
|
||||||
url: "/oauth/providers",
|
url: apiPrefix + "/oauth/providers",
|
||||||
method: "post",
|
method: "post",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,6 +19,11 @@
|
|||||||
<div class="helper">{{ t("certd.sys.setting.environmentVarsHelper") }}</div>
|
<div class="helper">{{ t("certd.sys.setting.environmentVarsHelper") }}</div>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
|
|
||||||
|
<a-form-item :label="t('certd.sys.setting.commonHeaders')" :name="['private', 'commonHeaders']">
|
||||||
|
<a-textarea v-model:value="formState.private.commonHeaders" :placeholder="commonHeadersExample" rows="4" />
|
||||||
|
<div class="helper">{{ t("certd.sys.setting.commonHeadersHelper") }}</div>
|
||||||
|
</a-form-item>
|
||||||
|
|
||||||
<a-form-item :label="t('certd.dualStackNetwork')" :name="['private', 'dnsResultOrder']">
|
<a-form-item :label="t('certd.dualStackNetwork')" :name="['private', 'dnsResultOrder']">
|
||||||
<a-select v-model:value="formState.private.dnsResultOrder">
|
<a-select v-model:value="formState.private.dnsResultOrder">
|
||||||
<a-select-option value="verbatim">{{ t("certd.default") }}</a-select-option>
|
<a-select-option value="verbatim">{{ t("certd.default") }}</a-select-option>
|
||||||
@@ -64,6 +69,10 @@ const environmentVarsExample = ref(
|
|||||||
`ALIYUN_CLIENT_CONNECT_TIMEOUT=16000 #连接超时,单位毫秒
|
`ALIYUN_CLIENT_CONNECT_TIMEOUT=16000 #连接超时,单位毫秒
|
||||||
ALIYUN_CLIENT_READ_TIMEOUT=16000 #读取数据超时,单位毫秒`
|
ALIYUN_CLIENT_READ_TIMEOUT=16000 #读取数据超时,单位毫秒`
|
||||||
);
|
);
|
||||||
|
const commonHeadersExample = ref(
|
||||||
|
`User-Agent=certd
|
||||||
|
X-Custom-Header=value`
|
||||||
|
);
|
||||||
|
|
||||||
const formState = reactive<Partial<SysSettings>>({
|
const formState = reactive<Partial<SysSettings>>({
|
||||||
public: {},
|
public: {},
|
||||||
|
|||||||
@@ -109,6 +109,7 @@ const formState = reactive<Partial<SysSettings>>({
|
|||||||
const oauthProviders = ref([]);
|
const oauthProviders = ref([]);
|
||||||
async function loadOauthProviders() {
|
async function loadOauthProviders() {
|
||||||
oauthProviders.value = await api.GetOauthProviders();
|
oauthProviders.value = await api.GetOauthProviders();
|
||||||
|
mergeOauthProviderSettings();
|
||||||
}
|
}
|
||||||
|
|
||||||
const bindDomain = computed(() => {
|
const bindDomain = computed(() => {
|
||||||
@@ -164,6 +165,16 @@ const onFinish = async (form: any) => {
|
|||||||
function buildCallbackUrl(type: string) {
|
function buildCallbackUrl(type: string) {
|
||||||
return `${window.location.origin}/api/oauth/callback/${type}`;
|
return `${window.location.origin}/api/oauth/callback/${type}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function mergeOauthProviderSettings() {
|
||||||
|
const savedProviders = formState.public?.oauthProviders || {};
|
||||||
|
for (const item of oauthProviders.value) {
|
||||||
|
const saved = savedProviders[item.name];
|
||||||
|
if (saved) {
|
||||||
|
item.addonId = saved.addonId;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
<style lang="less">
|
<style lang="less">
|
||||||
.sys-settings-oauth {
|
.sys-settings-oauth {
|
||||||
|
|||||||
@@ -3,6 +3,43 @@
|
|||||||
All notable changes to this project will be documented in this file.
|
All notable changes to this project will be documented in this file.
|
||||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||||
|
|
||||||
|
# [1.40.0](https://github.com/certd/certd/compare/v1.39.16...v1.40.0) (2026-05-14)
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* 修复群晖授权没有显示设备id输入框的bug ([2f172b5](https://github.com/certd/certd/commit/2f172b56e9411303ca15138d827bdb9bafdae4d1))
|
||||||
|
* 修复clogin登录丢失state问题 ([22f5cfc](https://github.com/certd/certd/commit/22f5cfcfd8462ca74128329eefb3f48b3ee0b7ea))
|
||||||
|
* 修复clogin多选类型登录失败的bug ([9f878a3](https://github.com/certd/certd/commit/9f878a353cd49b7b10bb0a95610ad236bc920dd2))
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* 彩虹登录支持选择多种登录方式 ([7aa0c7e](https://github.com/certd/certd/commit/7aa0c7e491fe660abb62e68792ff5474f19bd5b8))
|
||||||
|
|
||||||
|
### Performance Improvements
|
||||||
|
|
||||||
|
* 第三方登录自动注册的用户支持设置初始化密码 ([a815d02](https://github.com/certd/certd/commit/a815d0245b97efbb948b33d6fc9d49862ce06889))
|
||||||
|
* 头像增加缓存时间 ([7015b1b](https://github.com/certd/certd/commit/7015b1b232602e5168a3eb8bee6d7f1776ae1e74))
|
||||||
|
|
||||||
|
## [1.39.16](https://github.com/certd/certd/compare/v1.39.15...v1.39.16) (2026-05-13)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @certd/ui-server
|
||||||
|
|
||||||
|
## [1.39.15](https://github.com/certd/certd/compare/v1.39.14...v1.39.15) (2026-05-13)
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* 修复第三方登录彩虹登录不上的bug ([bae4f8e](https://github.com/certd/certd/commit/bae4f8e3209d9f9869ecbd7c01655383bac2fe21))
|
||||||
|
|
||||||
|
### Performance Improvements
|
||||||
|
|
||||||
|
* icon选择器增加一套logo集 ([fdd5848](https://github.com/certd/certd/commit/fdd5848df4055a6ee07dc5eabaaf6b718672882d))
|
||||||
|
|
||||||
|
## [1.39.14](https://github.com/certd/certd/compare/v1.39.13...v1.39.14) (2026-05-11)
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* 修复启动时报密钥备份不存在的问题 ([c966896](https://github.com/certd/certd/commit/c9668965226af6b54e0e576931dcba8b3d188ef3))
|
||||||
|
|
||||||
## [1.39.13](https://github.com/certd/certd/compare/v1.39.12...v1.39.13) (2026-05-10)
|
## [1.39.13](https://github.com/certd/certd/compare/v1.39.12...v1.39.13) (2026-05-10)
|
||||||
|
|
||||||
### Bug Fixes
|
### Bug Fixes
|
||||||
|
|||||||
@@ -39,6 +39,8 @@ input:
|
|||||||
component:
|
component:
|
||||||
name: a-switch
|
name: a-switch
|
||||||
vModel: checked
|
vModel: checked
|
||||||
|
col:
|
||||||
|
span: 24
|
||||||
helper: 是否启用了双重认证
|
helper: 是否启用了双重认证
|
||||||
required: true
|
required: true
|
||||||
deviceId:
|
deviceId:
|
||||||
@@ -48,6 +50,8 @@ input:
|
|||||||
name: synology-device-id-getter
|
name: synology-device-id-getter
|
||||||
type: access
|
type: access
|
||||||
typeName: synology
|
typeName: synology
|
||||||
|
col:
|
||||||
|
span: 24
|
||||||
mergeScript: |2-
|
mergeScript: |2-
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -14,43 +14,54 @@ input:
|
|||||||
loginType:
|
loginType:
|
||||||
title: 登录类型
|
title: 登录类型
|
||||||
component:
|
component:
|
||||||
name: a-auto-complete
|
name: a-select
|
||||||
|
vModel: value
|
||||||
|
mode: tags
|
||||||
|
multiple: true
|
||||||
options:
|
options:
|
||||||
- label: QQ
|
- label: QQ
|
||||||
value: qq
|
value: qq
|
||||||
|
icon: cib:tencent-qq:#007AFF
|
||||||
- label: 微信
|
- label: 微信
|
||||||
value: wx
|
value: wx
|
||||||
|
icon: simple-icons:wechat:#34C759
|
||||||
- label: 支付宝
|
- label: 支付宝
|
||||||
value: alipay
|
value: alipay
|
||||||
|
icon: simple-icons:alipay:#0099ff
|
||||||
- label: 微博
|
- label: 微博
|
||||||
value: sina
|
value: sina
|
||||||
|
icon: uiw:weibo:#FF3B30
|
||||||
- label: 百度
|
- label: 百度
|
||||||
value: baidu
|
value: baidu
|
||||||
|
icon: simple-icons:baidu:#007AFF
|
||||||
- label: 华为
|
- label: 华为
|
||||||
value: huawei
|
value: huawei
|
||||||
|
icon: simple-icons:huawei:#ff0000
|
||||||
- label: 小米
|
- label: 小米
|
||||||
value: xiaomi
|
value: xiaomi
|
||||||
|
icon: simple-icons:xiaomi:#FF9500
|
||||||
- label: 谷歌
|
- label: 谷歌
|
||||||
value: google
|
value: google
|
||||||
|
icon: flat-color-icons:google
|
||||||
- label: 微软
|
- label: 微软
|
||||||
value: microsoft
|
value: microsoft
|
||||||
|
icon: logos:microsoft-icon
|
||||||
- label: Facebook
|
- label: Facebook
|
||||||
value: facebook
|
value: facebook
|
||||||
|
icon: logos:facebook
|
||||||
- label: Twitter
|
- label: Twitter
|
||||||
value: twitter
|
value: twitter
|
||||||
|
icon: logos:twitter
|
||||||
- label: 钉钉
|
- label: 钉钉
|
||||||
value: dingtalk
|
value: dingtalk
|
||||||
|
icon: ant-design:dingding-outlined:#007AFF
|
||||||
- label: Gitee
|
- label: Gitee
|
||||||
value: gitee
|
value: gitee
|
||||||
|
icon: simple-icons:gitee:#c71d23
|
||||||
- label: Github
|
- label: Github
|
||||||
value: github
|
value: github
|
||||||
|
icon: logos:github-icon
|
||||||
required: true
|
required: true
|
||||||
icon:
|
|
||||||
title: 自定义图标
|
|
||||||
component:
|
|
||||||
name: fs-icon-selector
|
|
||||||
vModel: modelValue
|
|
||||||
required: false
|
|
||||||
appId:
|
appId:
|
||||||
title: AppId
|
title: AppId
|
||||||
helper: 彩虹聚合登录->应用列表->创建应用 获取
|
helper: 彩虹聚合登录->应用列表->创建应用 获取
|
||||||
|
|||||||
@@ -10,6 +10,18 @@ input:
|
|||||||
component:
|
component:
|
||||||
name: fs-icon-selector
|
name: fs-icon-selector
|
||||||
vModel: modelValue
|
vModel: modelValue
|
||||||
|
iconSets:
|
||||||
|
- streamline-logos
|
||||||
|
- logos
|
||||||
|
- fa-brands
|
||||||
|
- fa-solid
|
||||||
|
- fa-regular
|
||||||
|
- carbon
|
||||||
|
- ion
|
||||||
|
- ant-design
|
||||||
|
- mdi
|
||||||
|
- twemoji
|
||||||
|
- svg-spinners
|
||||||
required: false
|
required: false
|
||||||
clientId:
|
clientId:
|
||||||
title: ClientId
|
title: ClientId
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@certd/ui-server",
|
"name": "@certd/ui-server",
|
||||||
"version": "1.39.13",
|
"version": "1.40.0",
|
||||||
"description": "fast-server base midway",
|
"description": "fast-server base midway",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
@@ -53,20 +53,20 @@
|
|||||||
"@aws-sdk/client-sts": "^3.990.0",
|
"@aws-sdk/client-sts": "^3.990.0",
|
||||||
"@azure/arm-dns": "^5.1.0",
|
"@azure/arm-dns": "^5.1.0",
|
||||||
"@azure/identity": "^4.13.1",
|
"@azure/identity": "^4.13.1",
|
||||||
"@certd/acme-client": "^1.39.13",
|
"@certd/acme-client": "^1.40.0",
|
||||||
"@certd/basic": "^1.39.13",
|
"@certd/basic": "^1.40.0",
|
||||||
"@certd/commercial-core": "^1.39.13",
|
"@certd/commercial-core": "^1.40.0",
|
||||||
"@certd/cv4pve-api-javascript": "^8.4.2",
|
"@certd/cv4pve-api-javascript": "^8.4.2",
|
||||||
"@certd/jdcloud": "^1.39.13",
|
"@certd/jdcloud": "^1.40.0",
|
||||||
"@certd/lib-huawei": "^1.39.13",
|
"@certd/lib-huawei": "^1.40.0",
|
||||||
"@certd/lib-k8s": "^1.39.13",
|
"@certd/lib-k8s": "^1.40.0",
|
||||||
"@certd/lib-server": "^1.39.13",
|
"@certd/lib-server": "^1.40.0",
|
||||||
"@certd/midway-flyway-js": "^1.39.13",
|
"@certd/midway-flyway-js": "^1.40.0",
|
||||||
"@certd/pipeline": "^1.39.13",
|
"@certd/pipeline": "^1.40.0",
|
||||||
"@certd/plugin-cert": "^1.39.13",
|
"@certd/plugin-cert": "^1.40.0",
|
||||||
"@certd/plugin-lib": "^1.39.13",
|
"@certd/plugin-lib": "^1.40.0",
|
||||||
"@certd/plugin-plus": "^1.39.13",
|
"@certd/plugin-plus": "^1.40.0",
|
||||||
"@certd/plus-core": "^1.39.13",
|
"@certd/plus-core": "^1.40.0",
|
||||||
"@google-cloud/dns": "^5.3.1",
|
"@google-cloud/dns": "^5.3.1",
|
||||||
"@google-cloud/publicca": "^1.3.0",
|
"@google-cloud/publicca": "^1.3.0",
|
||||||
"@huaweicloud/huaweicloud-sdk-cdn": "^3.1.185",
|
"@huaweicloud/huaweicloud-sdk-cdn": "^3.1.185",
|
||||||
|
|||||||
@@ -0,0 +1,6 @@
|
|||||||
|
export function shouldSetDefaultNoCache(path: string, cacheControl?: string) {
|
||||||
|
if (cacheControl) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return path === '/' || path === '/index.html' || path.startsWith('/api');
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
/// <reference types="mocha" />
|
||||||
|
/// <reference types="node" />
|
||||||
|
|
||||||
|
import assert from "node:assert/strict";
|
||||||
|
|
||||||
|
import { shouldSetDefaultNoCache } from "./configuration-cache.js";
|
||||||
|
|
||||||
|
describe("shouldSetDefaultNoCache", () => {
|
||||||
|
it("sets default no-cache for html and api responses without cache headers", () => {
|
||||||
|
assert.equal(shouldSetDefaultNoCache("/"), true);
|
||||||
|
assert.equal(shouldSetDefaultNoCache("/index.html"), true);
|
||||||
|
assert.equal(shouldSetDefaultNoCache("/api/basic/file/download"), true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("keeps explicit cache headers from file responses", () => {
|
||||||
|
assert.equal(shouldSetDefaultNoCache("/api/basic/file/download", "public,max-age=259200"), false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("ignores non-html and non-api paths", () => {
|
||||||
|
assert.equal(shouldSetDefaultNoCache("/static/images/logo.svg"), false);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -20,6 +20,7 @@ import * as commercial from '@certd/commercial-core';
|
|||||||
import * as upload from '@midwayjs/upload';
|
import * as upload from '@midwayjs/upload';
|
||||||
import { setLogger } from '@certd/acme-client';
|
import { setLogger } from '@certd/acme-client';
|
||||||
import {HiddenMiddleware} from "./middleware/hidden.js";
|
import {HiddenMiddleware} from "./middleware/hidden.js";
|
||||||
|
import { shouldSetDefaultNoCache } from './configuration-cache.js';
|
||||||
// import * as swagger from '@midwayjs/swagger';
|
// import * as swagger from '@midwayjs/swagger';
|
||||||
//@ts-ignore
|
//@ts-ignore
|
||||||
// process.env.UV_THREADPOOL_SIZE = 2
|
// process.env.UV_THREADPOOL_SIZE = 2
|
||||||
@@ -123,7 +124,7 @@ export class MainConfiguration {
|
|||||||
|
|
||||||
this.app.getMiddleware().insertFirst(async (ctx: IMidwayKoaContext, next: NextFunction) => {
|
this.app.getMiddleware().insertFirst(async (ctx: IMidwayKoaContext, next: NextFunction) => {
|
||||||
await next();
|
await next();
|
||||||
if (ctx.path === '/' || ctx.path === '/index.html' || ctx.path.startsWith("/api")) {
|
if (shouldSetDefaultNoCache(ctx.path, ctx.response.get('Cache-Control'))) {
|
||||||
ctx.response.set('Cache-Control', 'public,max-age=0');
|
ctx.response.set('Cache-Control', 'public,max-age=0');
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -0,0 +1,39 @@
|
|||||||
|
/// <reference types="mocha" />
|
||||||
|
/// <reference types="node" />
|
||||||
|
|
||||||
|
import assert from "node:assert/strict";
|
||||||
|
|
||||||
|
import { getImageDownloadOptions, isImageFile } from "./file-controller.js";
|
||||||
|
|
||||||
|
describe("FileController.isImageFile", () => {
|
||||||
|
it("detects uploaded logo image files", () => {
|
||||||
|
assert.equal(isImageFile("data/upload/public/user/logo.PNG"), true);
|
||||||
|
assert.equal(isImageFile("data/upload/public/user/logo.svg"), true);
|
||||||
|
assert.equal(isImageFile("data/upload/public/user/logo.webp"), true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("does not treat non-image downloads as logo images", () => {
|
||||||
|
assert.equal(isImageFile("data/upload/public/user/archive.zip"), false);
|
||||||
|
assert.equal(isImageFile("data/upload/public/user/cert.pem"), false);
|
||||||
|
assert.equal(isImageFile("data/upload/public/user/logo"), false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("builds koa-send options that keep image cache headers at 3 days", () => {
|
||||||
|
const options = getImageDownloadOptions("data/upload/public/user/logo.png");
|
||||||
|
|
||||||
|
assert.equal(options?.maxage, 259200000);
|
||||||
|
|
||||||
|
const headers: Record<string, string> = {};
|
||||||
|
options?.setHeaders({
|
||||||
|
setHeader(key: string, value: string) {
|
||||||
|
headers[key] = value;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
assert.equal(headers["Cache-Control"], "public,max-age=259200");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("does not build cache options for non-image files", () => {
|
||||||
|
assert.equal(getImageDownloadOptions("data/upload/private/user/cert.pem"), undefined);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -5,6 +5,25 @@ import { nanoid } from 'nanoid';
|
|||||||
import { cache } from '@certd/basic';
|
import { cache } from '@certd/basic';
|
||||||
import { UploadFileInfo } from '@midwayjs/upload';
|
import { UploadFileInfo } from '@midwayjs/upload';
|
||||||
|
|
||||||
|
const imageExtSet = new Set(['.apng', '.avif', '.bmp', '.gif', '.ico', '.jpeg', '.jpg', '.png', '.svg', '.webp']);
|
||||||
|
const imageCacheSeconds = 3 * 24 * 60 * 60;
|
||||||
|
|
||||||
|
export function isImageFile(filePath: string) {
|
||||||
|
return imageExtSet.has(filePath.substring(filePath.lastIndexOf('.')).toLowerCase());
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getImageDownloadOptions(filePath: string) {
|
||||||
|
if (!isImageFile(filePath)) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
maxage: imageCacheSeconds * 1000,
|
||||||
|
setHeaders(res: any) {
|
||||||
|
res.setHeader('Cache-Control', `public,max-age=${imageCacheSeconds}`);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*/
|
*/
|
||||||
@Provide()
|
@Provide()
|
||||||
@@ -40,8 +59,10 @@ export class FileController extends BaseController {
|
|||||||
userId = this.getUserId();
|
userId = this.getUserId();
|
||||||
}
|
}
|
||||||
const filePath = this.fileService.getFile(key, userId);
|
const filePath = this.fileService.getFile(key, userId);
|
||||||
|
const sendOptions = getImageDownloadOptions(filePath);
|
||||||
|
if (!sendOptions) {
|
||||||
this.ctx.response.attachment(filePath);
|
this.ctx.response.attachment(filePath);
|
||||||
this.ctx.response.set('Cache-Control', 'public,max-age=2592000');
|
}
|
||||||
await send(this.ctx, filePath);
|
await send(this.ctx, filePath, sendOptions);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,6 +11,24 @@ import { UserEntity } from "../../../modules/sys/authority/entity/user.js";
|
|||||||
import { UserService } from "../../../modules/sys/authority/service/user-service.js";
|
import { UserService } from "../../../modules/sys/authority/service/user-service.js";
|
||||||
import { IOauthProvider } from "../../../plugins/plugin-oauth/api.js";
|
import { IOauthProvider } from "../../../plugins/plugin-oauth/api.js";
|
||||||
|
|
||||||
|
type OauthProviderSetting = {
|
||||||
|
type: string;
|
||||||
|
title: string;
|
||||||
|
icon?: string;
|
||||||
|
addonId: number;
|
||||||
|
types?: OauthProviderType[];
|
||||||
|
};
|
||||||
|
|
||||||
|
type OauthProviderType = {
|
||||||
|
type: string;
|
||||||
|
name: string;
|
||||||
|
icon?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
function getOauthBoundType(type: string, subtype?: string) {
|
||||||
|
return subtype ? `${type}:${subtype}` : type;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*/
|
*/
|
||||||
@Provide()
|
@Provide()
|
||||||
@@ -41,7 +59,7 @@ export class ConnectController extends BaseController {
|
|||||||
if (!publicSettings?.oauthEnabled) {
|
if (!publicSettings?.oauthEnabled) {
|
||||||
throw new Error("OAuth功能未启用");
|
throw new Error("OAuth功能未启用");
|
||||||
}
|
}
|
||||||
const setting = publicSettings?.oauthProviders?.[type || ""]
|
const setting = publicSettings?.oauthProviders?.[type || ""] as OauthProviderSetting | undefined;
|
||||||
if (!setting) {
|
if (!setting) {
|
||||||
throw new Error(`未配置该OAuth类型:${type}`);
|
throw new Error(`未配置该OAuth类型:${type}`);
|
||||||
}
|
}
|
||||||
@@ -50,19 +68,38 @@ export class ConnectController extends BaseController {
|
|||||||
if (!addon) {
|
if (!addon) {
|
||||||
throw new Error("初始化OAuth插件失败");
|
throw new Error("初始化OAuth插件失败");
|
||||||
}
|
}
|
||||||
return addon as IOauthProvider;
|
return {
|
||||||
|
addon: addon as IOauthProvider,
|
||||||
|
setting,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@Post('/login', { description: Constants.per.guest })
|
@Post('/login', { description: Constants.per.guest })
|
||||||
public async login(@Body(ALL) body: { type: string, forType?:string ,from?:string }) {
|
public async login(@Body(ALL) body: { type: string, subtype?: string, forType?:string ,from?:string }) {
|
||||||
|
|
||||||
const addon = await this.getOauthProvider(body.type);
|
const oauthProvider = await this.getOauthProvider(body.type);
|
||||||
const installInfo = await this.sysSettingsService.getSetting<SysInstallInfo>(SysInstallInfo);
|
const installInfo = await this.sysSettingsService.getSetting<SysInstallInfo>(SysInstallInfo);
|
||||||
const bindUrl = installInfo?.bindUrl || "";
|
const bindUrl = installInfo?.bindUrl || "";
|
||||||
//构造登录url
|
//构造登录url
|
||||||
const redirectUrl = `${bindUrl}api/oauth/callback/${body.type}`;
|
const redirectUrl = `${bindUrl}api/oauth/callback/${body.type}`;
|
||||||
const { loginUrl, ticketValue } = await addon.buildLoginUrl({ redirectUri: redirectUrl, forType: body.forType ,from: body.from || "web" });
|
|
||||||
const ticket = this.codeService.setValidationValue(ticketValue)
|
let stateObj = {
|
||||||
|
forType: body.forType || 'login',
|
||||||
|
}
|
||||||
|
const state = utils.hash.base64(JSON.stringify(stateObj))
|
||||||
|
const { loginUrl, ticketValue } = await oauthProvider.addon.buildLoginUrl({
|
||||||
|
redirectUri: redirectUrl,
|
||||||
|
forType: body.forType,
|
||||||
|
from: body.from || "web",
|
||||||
|
subtype: body.subtype,
|
||||||
|
state,
|
||||||
|
});
|
||||||
|
|
||||||
|
const ticket = this.codeService.setValidationValue({
|
||||||
|
...ticketValue,
|
||||||
|
state,
|
||||||
|
subtype: body.subtype,
|
||||||
|
})
|
||||||
this.ctx.cookies.set("oauth_ticket", ticket, {
|
this.ctx.cookies.set("oauth_ticket", ticket, {
|
||||||
httpOnly: true,
|
httpOnly: true,
|
||||||
// secure: true,
|
// secure: true,
|
||||||
@@ -78,7 +115,7 @@ export class ConnectController extends BaseController {
|
|||||||
checkPlus()
|
checkPlus()
|
||||||
|
|
||||||
//处理登录回调
|
//处理登录回调
|
||||||
const addon = await this.getOauthProvider(type);
|
const oauthProvider = await this.getOauthProvider(type);
|
||||||
const request = this.ctx.request;
|
const request = this.ctx.request;
|
||||||
// const ticketValue = this.codeService.getValidationValue(ticket);
|
// const ticketValue = this.codeService.getValidationValue(ticket);
|
||||||
// if (!ticketValue) {
|
// if (!ticketValue) {
|
||||||
@@ -98,7 +135,7 @@ export class ConnectController extends BaseController {
|
|||||||
const bindUrl = installInfo?.bindUrl || "";
|
const bindUrl = installInfo?.bindUrl || "";
|
||||||
const currentUrl = `${bindUrl}api/oauth/callback/${type}?${request.querystring}`
|
const currentUrl = `${bindUrl}api/oauth/callback/${type}?${request.querystring}`
|
||||||
try {
|
try {
|
||||||
const tokenRes = await addon.onCallback({
|
const tokenRes = await oauthProvider.addon.onCallback({
|
||||||
code: query.code,
|
code: query.code,
|
||||||
state: query.state,
|
state: query.state,
|
||||||
ticketValue,
|
ticketValue,
|
||||||
@@ -108,11 +145,14 @@ export class ConnectController extends BaseController {
|
|||||||
const userInfo = tokenRes.userInfo;
|
const userInfo = tokenRes.userInfo;
|
||||||
|
|
||||||
const validationCode = await this.codeService.setValidationValue({
|
const validationCode = await this.codeService.setValidationValue({
|
||||||
type,
|
type: getOauthBoundType(type, ticketValue.subtype),
|
||||||
userInfo,
|
userInfo,
|
||||||
});
|
});
|
||||||
|
|
||||||
const state = JSON.parse(utils.hash.base64Decode(query.state));
|
let state = {forType:""}
|
||||||
|
if (query.state) {
|
||||||
|
state = JSON.parse(utils.hash.base64Decode(query.state));
|
||||||
|
}
|
||||||
|
|
||||||
const redirectUrl = `${bindUrl}#/oauth/callback/${type}?validationCode=${validationCode}&forType=${state.forType}`;
|
const redirectUrl = `${bindUrl}#/oauth/callback/${type}?validationCode=${validationCode}&forType=${state.forType}`;
|
||||||
this.ctx.redirect(redirectUrl);
|
this.ctx.redirect(redirectUrl);
|
||||||
@@ -126,8 +166,10 @@ export class ConnectController extends BaseController {
|
|||||||
@Post('/getLogoutUrl', { description: Constants.per.guest })
|
@Post('/getLogoutUrl', { description: Constants.per.guest })
|
||||||
public async logout(@Body(ALL) body: any) {
|
public async logout(@Body(ALL) body: any) {
|
||||||
checkPlus()
|
checkPlus()
|
||||||
const addon = await this.getOauthProvider(body.type);
|
const oauthProvider = await this.getOauthProvider(body.type);
|
||||||
const { logoutUrl } = await addon.buildLogoutUrl(body);
|
const { logoutUrl } = await oauthProvider.addon.buildLogoutUrl({
|
||||||
|
...body,
|
||||||
|
});
|
||||||
return this.ok({ logoutUrl });
|
return this.ok({ logoutUrl });
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -141,7 +183,7 @@ export class ConnectController extends BaseController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const type = validationValue.type;
|
const type = validationValue.type;
|
||||||
if (type !== body.type) {
|
if (type !== body.type && !type.startsWith(`${body.type}:`)) {
|
||||||
throw new Error("校验码错误");
|
throw new Error("校验码错误");
|
||||||
}
|
}
|
||||||
const userInfo = validationValue.userInfo;
|
const userInfo = validationValue.userInfo;
|
||||||
@@ -259,16 +301,32 @@ export class ConnectController extends BaseController {
|
|||||||
provider.addonId = conf.addonId;
|
provider.addonId = conf.addonId;
|
||||||
provider.addonTitle = addonEntity.name;
|
provider.addonTitle = addonEntity.name;
|
||||||
|
|
||||||
const addon = await this.addonGetterService.getAddonById(conf.addonId,true,0,null);
|
const addon = await this.addonGetterService.getAddonById(conf.addonId,true,0,null) as IOauthProvider & { icon?: string; types?: OauthProviderType[] };
|
||||||
const {logoutUrl} = await addon.buildLogoutUrl();
|
const {logoutUrl} = await addon.buildLogoutUrl({});
|
||||||
if (logoutUrl){
|
if (logoutUrl){
|
||||||
provider.logoutUrl = logoutUrl;
|
provider.logoutUrl = logoutUrl;
|
||||||
}
|
}
|
||||||
if(addon.icon){
|
if(addon.icon){
|
||||||
provider.icon = addon.icon;
|
provider.icon = addon.icon;
|
||||||
}
|
}
|
||||||
|
if(addon.types?.length){
|
||||||
|
provider.types = addon.types;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
if (provider.addonId && provider.types?.length) {
|
||||||
|
for (const subtype of provider.types) {
|
||||||
|
list.push({
|
||||||
|
...provider,
|
||||||
|
name: type,
|
||||||
|
subtype: subtype.type,
|
||||||
|
title: subtype.name,
|
||||||
|
icon: subtype.icon || provider.icon,
|
||||||
|
addonTitle: subtype.name,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
list.push(provider);
|
list.push(provider);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -135,6 +135,7 @@ export class SysSettingsController extends CrudController<SysSettingsService> {
|
|||||||
await this.service.savePrivateSettings(privateSettings);
|
await this.service.savePrivateSettings(privateSettings);
|
||||||
return this.ok({});
|
return this.ok({});
|
||||||
}
|
}
|
||||||
|
|
||||||
@Post('/stopOtherUserTimer', { description: 'sys:settings:edit' })
|
@Post('/stopOtherUserTimer', { description: 'sys:settings:edit' })
|
||||||
async stopOtherUserTimer(@Body(ALL) body) {
|
async stopOtherUserTimer(@Body(ALL) body) {
|
||||||
await this.pipelineService.stopOtherUserPipeline(1);
|
await this.pipelineService.stopOtherUserPipeline(1);
|
||||||
|
|||||||
@@ -1,16 +1,16 @@
|
|||||||
import { BaseController, Constants, SysSettingsService } from '@certd/lib-server';
|
import { BaseController, Constants, SysSettingsService } from "@certd/lib-server";
|
||||||
import { ALL, Body, Controller, Inject, Post, Provide } from '@midwayjs/core';
|
import { ALL, Body, Controller, Inject, Post, Provide } from "@midwayjs/core";
|
||||||
import { PasskeyService } from '../../../modules/login/service/passkey-service.js';
|
import { PasskeyService } from "../../../modules/login/service/passkey-service.js";
|
||||||
import { RoleService } from '../../../modules/sys/authority/service/role-service.js';
|
import { RoleService } from "../../../modules/sys/authority/service/role-service.js";
|
||||||
import { UserService } from '../../../modules/sys/authority/service/user-service.js';
|
import { UserService } from "../../../modules/sys/authority/service/user-service.js";
|
||||||
import { ApiTags } from '@midwayjs/swagger';
|
import { ApiTags } from "@midwayjs/swagger";
|
||||||
import { CodeService } from '../../../modules/basic/service/code-service.js';
|
import { CodeService } from "../../../modules/basic/service/code-service.js";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*/
|
*/
|
||||||
@Provide()
|
@Provide()
|
||||||
@Controller('/api/mine')
|
@Controller("/api/mine")
|
||||||
@ApiTags(['mine'])
|
@ApiTags(["mine"])
|
||||||
export class MineController extends BaseController {
|
export class MineController extends BaseController {
|
||||||
@Inject()
|
@Inject()
|
||||||
userService: UserService;
|
userService: UserService;
|
||||||
@@ -27,28 +27,38 @@ export class MineController extends BaseController {
|
|||||||
@Inject()
|
@Inject()
|
||||||
sysSettingsService: SysSettingsService;
|
sysSettingsService: SysSettingsService;
|
||||||
|
|
||||||
@Post('/info', { description: Constants.per.authOnly, summary: '查询用户信息' })
|
@Post("/info", { description: Constants.per.authOnly, summary: "查询用户信息" })
|
||||||
public async info() {
|
public async info() {
|
||||||
const userId = this.getUserId();
|
const userId = this.getUserId();
|
||||||
const user = await this.userService.info(userId);
|
const user = await this.userService.info(userId);
|
||||||
const isWeak = await this.userService.checkPassword('123456', user.password, user.passwordVersion);
|
const isWeak = await this.userService.checkPassword("123456", user.password, user.passwordVersion);
|
||||||
if (isWeak) {
|
if (isWeak) {
|
||||||
//@ts-ignore
|
//@ts-ignore
|
||||||
user.isWeak = true;
|
user.isWeak = true;
|
||||||
}
|
}
|
||||||
|
const needInitPassword = user.password === "changeme";
|
||||||
user.roleIds = await this.roleService.getRoleIdsByUserId(userId);
|
user.roleIds = await this.roleService.getRoleIdsByUserId(userId);
|
||||||
delete user.password;
|
delete user.password;
|
||||||
|
//@ts-ignore
|
||||||
|
user.needInitPassword = needInitPassword;
|
||||||
return this.ok(user);
|
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) {
|
public async changePassword(@Body(ALL) body: any) {
|
||||||
const userId = this.getUserId();
|
const userId = this.getUserId();
|
||||||
await this.userService.changePassword(userId, body);
|
await this.userService.changePassword(userId, body);
|
||||||
return this.ok({});
|
return this.ok({});
|
||||||
}
|
}
|
||||||
|
|
||||||
@Post('/updateProfile', { description: Constants.per.authOnly, summary: '更新用户资料' })
|
@Post("/initPassword", { description: Constants.per.authOnly, summary: "初始化密码" })
|
||||||
|
public async initPassword(@Body(ALL) body: any) {
|
||||||
|
const userId = this.getUserId();
|
||||||
|
await this.userService.initPassword(userId, body);
|
||||||
|
return this.ok({});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Post("/updateProfile", { description: Constants.per.authOnly, summary: "更新用户资料" })
|
||||||
public async updateProfile(@Body(ALL) body: any) {
|
public async updateProfile(@Body(ALL) body: any) {
|
||||||
const userId = this.getUserId();
|
const userId = this.getUserId();
|
||||||
|
|
||||||
@@ -59,7 +69,7 @@ export class MineController extends BaseController {
|
|||||||
return this.ok({});
|
return this.ok({});
|
||||||
}
|
}
|
||||||
|
|
||||||
@Post('/contact/capability', { description: Constants.per.authOnly, summary: '查询联系方式绑定能力' })
|
@Post("/contact/capability", { description: Constants.per.authOnly, summary: "查询联系方式绑定能力" })
|
||||||
public async contactCapability() {
|
public async contactCapability() {
|
||||||
const settings = await this.sysSettingsService.getPrivateSettings();
|
const settings = await this.sysSettingsService.getPrivateSettings();
|
||||||
return this.ok({
|
return this.ok({
|
||||||
@@ -67,27 +77,27 @@ export class MineController extends BaseController {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@Post('/contact/verifyIdentity', { description: Constants.per.authOnly, summary: '验证本人操作' })
|
@Post("/contact/verifyIdentity", { description: Constants.per.authOnly, summary: "验证本人操作" })
|
||||||
public async verifyContactIdentity(@Body(ALL) body: { identityType: 'password' | 'email' | 'mobile'; identityPassword?: string; identityValidateCode?: string }) {
|
public async verifyContactIdentity(@Body(ALL) body: { identityType: "password" | "email" | "mobile"; identityPassword?: string; identityValidateCode?: string }) {
|
||||||
const userId = this.getUserId();
|
const userId = this.getUserId();
|
||||||
await this.userService.verifyIdentity(userId, body, this.codeService);
|
await this.userService.verifyIdentity(userId, body, this.codeService);
|
||||||
const validationCode = this.codeService.setValidationValue({
|
const validationCode = this.codeService.setValidationValue({
|
||||||
type: 'contactIdentity',
|
type: "contactIdentity",
|
||||||
userId,
|
userId,
|
||||||
identityType: body.identityType,
|
identityType: body.identityType,
|
||||||
});
|
});
|
||||||
return this.ok({ validationCode });
|
return this.ok({ validationCode });
|
||||||
}
|
}
|
||||||
|
|
||||||
@Post('/contact/mobile', { description: Constants.per.authOnly, summary: '绑定或修改手机号' })
|
@Post("/contact/mobile", { description: Constants.per.authOnly, summary: "绑定或修改手机号" })
|
||||||
public async updateMobile(@Body(ALL) body: { phoneCode?: string; mobile: string; validateCode: string; identityValidationCode: string }) {
|
public async updateMobile(@Body(ALL) body: { phoneCode?: string; mobile: string; validateCode: string; identityValidationCode: string }) {
|
||||||
const userId = this.getUserId();
|
const userId = this.getUserId();
|
||||||
this.userService.checkContactIdentityValidation(userId, body.identityValidationCode, this.codeService);
|
this.userService.checkContactIdentityValidation(userId, body.identityValidationCode, this.codeService);
|
||||||
await this.codeService.checkSmsCode({
|
await this.codeService.checkSmsCode({
|
||||||
mobile: body.mobile,
|
mobile: body.mobile,
|
||||||
phoneCode: body.phoneCode || '86',
|
phoneCode: body.phoneCode || "86",
|
||||||
smsCode: body.validateCode,
|
smsCode: body.validateCode,
|
||||||
verificationType: 'bindMobile',
|
verificationType: "bindMobile",
|
||||||
throwError: true,
|
throwError: true,
|
||||||
});
|
});
|
||||||
await this.userService.updateMobile(userId, {
|
await this.userService.updateMobile(userId, {
|
||||||
@@ -97,14 +107,14 @@ export class MineController extends BaseController {
|
|||||||
return this.ok({});
|
return this.ok({});
|
||||||
}
|
}
|
||||||
|
|
||||||
@Post('/contact/email', { description: Constants.per.authOnly, summary: '绑定或修改邮箱' })
|
@Post("/contact/email", { description: Constants.per.authOnly, summary: "绑定或修改邮箱" })
|
||||||
public async updateEmail(@Body(ALL) body: { email: string; validateCode: string; identityValidationCode: string }) {
|
public async updateEmail(@Body(ALL) body: { email: string; validateCode: string; identityValidationCode: string }) {
|
||||||
const userId = this.getUserId();
|
const userId = this.getUserId();
|
||||||
this.userService.checkContactIdentityValidation(userId, body.identityValidationCode, this.codeService);
|
this.userService.checkContactIdentityValidation(userId, body.identityValidationCode, this.codeService);
|
||||||
this.codeService.checkEmailCode({
|
this.codeService.checkEmailCode({
|
||||||
email: body.email,
|
email: body.email,
|
||||||
validateCode: body.validateCode,
|
validateCode: body.validateCode,
|
||||||
verificationType: 'bindEmail',
|
verificationType: "bindEmail",
|
||||||
throwError: true,
|
throwError: true,
|
||||||
});
|
});
|
||||||
await this.userService.updateEmail(userId, {
|
await this.userService.updateEmail(userId, {
|
||||||
|
|||||||
+1
-1
@@ -9,7 +9,7 @@ import { AutoPrint } from "./auto-print.js";
|
|||||||
|
|
||||||
@Autoload()
|
@Autoload()
|
||||||
@Scope(ScopeEnum.Request, { allowDowngrade: true })
|
@Scope(ScopeEnum.Request, { allowDowngrade: true })
|
||||||
export class AutoRegister {
|
export class AutoARegister { //这个A是必须,让他排在第一个 进行init,否则会被其他init模块抢先注册导致报错
|
||||||
@Inject()
|
@Inject()
|
||||||
autoInitSite: AutoInitSite;
|
autoInitSite: AutoInitSite;
|
||||||
|
|
||||||
@@ -1,12 +1,14 @@
|
|||||||
import assert from "assert";
|
import assert from "assert";
|
||||||
import esmock from "esmock";
|
import esmock from "esmock";
|
||||||
import { AutoFix, buildEabAccountKeyValue, buildLegacyGoogleAccountConfigWhere, parseStorageValue } from "./auto-fix.js";
|
import { AutoFix, buildEabAccountKeyValue, buildLegacyGoogleAccountConfigWhere, buildOauthBoundType, parseStorageValue } from "./auto-fix.js";
|
||||||
|
|
||||||
function createAutoFix(options: { pluginConfigService?: any; accessService?: any; storageService?: any }) {
|
function createAutoFix(options: { pluginConfigService?: any; accessService?: any; storageService?: any; sysSettingsService?: any; oauthBoundService?: any }) {
|
||||||
const autoFix = new AutoFix();
|
const autoFix = new AutoFix();
|
||||||
autoFix.pluginConfigService = options.pluginConfigService;
|
autoFix.pluginConfigService = options.pluginConfigService;
|
||||||
autoFix.accessService = options.accessService;
|
autoFix.accessService = options.accessService;
|
||||||
autoFix.storageService = options.storageService;
|
autoFix.storageService = options.storageService;
|
||||||
|
autoFix.sysSettingsService = options.sysSettingsService;
|
||||||
|
autoFix.oauthBoundService = options.oauthBoundService;
|
||||||
return autoFix;
|
return autoFix;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -42,6 +44,11 @@ describe("AutoFix", () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("builds OAuth subtype bound type", () => {
|
||||||
|
assert.equal(buildOauthBoundType("clogin", "alipay"), "clogin:alipay");
|
||||||
|
assert.equal(buildOauthBoundType("github"), "github");
|
||||||
|
});
|
||||||
|
|
||||||
it("finds legacy Google account config by exact email key only", async () => {
|
it("finds legacy Google account config by exact email key only", async () => {
|
||||||
let findOneWhere: any;
|
let findOneWhere: any;
|
||||||
let findCalled = false;
|
let findCalled = false;
|
||||||
@@ -107,6 +114,25 @@ describe("AutoFix", () => {
|
|||||||
} as any,
|
} as any,
|
||||||
accessService: null as any,
|
accessService: null as any,
|
||||||
storageService: null as any,
|
storageService: null as any,
|
||||||
|
sysSettingsService: {
|
||||||
|
async getPublicSettings() {
|
||||||
|
return {
|
||||||
|
oauthProviders: {},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
},
|
||||||
|
oauthBoundService: {
|
||||||
|
async transaction(callback: any) {
|
||||||
|
return await callback({
|
||||||
|
async findOne() {
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
async update() {
|
||||||
|
return { affected: 0 };
|
||||||
|
},
|
||||||
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
await autoFix.init();
|
await autoFix.init();
|
||||||
@@ -179,4 +205,97 @@ describe("AutoFix", () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("fixes legacy OAuth bound type from string addon loginType and converts loginType to array", async () => {
|
||||||
|
const updates: any[] = [];
|
||||||
|
const autoFix = createAutoFix({
|
||||||
|
pluginConfigService: null as any,
|
||||||
|
accessService: null as any,
|
||||||
|
storageService: null as any,
|
||||||
|
sysSettingsService: {
|
||||||
|
async getPublicSettings() {
|
||||||
|
return {
|
||||||
|
oauthProviders: {
|
||||||
|
clogin: {
|
||||||
|
addonId: 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
},
|
||||||
|
oauthBoundService: {
|
||||||
|
async transaction(callback: any) {
|
||||||
|
return await callback({
|
||||||
|
async findOne(entity: any, options: any) {
|
||||||
|
assert.equal(entity.name, "AddonEntity");
|
||||||
|
assert.deepEqual(options, { where: { id: 1 } });
|
||||||
|
return {
|
||||||
|
id: 1,
|
||||||
|
setting: JSON.stringify({
|
||||||
|
loginType: "alipay",
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
},
|
||||||
|
async update(entity: any, where: any, value: any) {
|
||||||
|
updates.push({ entity: entity.name, where, value });
|
||||||
|
return { affected: entity.name === "OauthBoundEntity" ? 1 : 0 };
|
||||||
|
},
|
||||||
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
await autoFix.fixOauthSubtypeBoundType();
|
||||||
|
|
||||||
|
assert.deepEqual(updates[0], {
|
||||||
|
entity: "OauthBoundEntity",
|
||||||
|
where: { type: "clogin" },
|
||||||
|
value: { type: "clogin:alipay" },
|
||||||
|
});
|
||||||
|
assert.equal(updates[1].entity, "AddonEntity");
|
||||||
|
assert.deepEqual(updates[1].where, { id: 1 });
|
||||||
|
assert.deepEqual(JSON.parse(updates[1].value.setting).loginType, ["alipay"]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("skips OAuth subtype fix when addon loginType is already not legacy string", async () => {
|
||||||
|
let updateCalled = false;
|
||||||
|
const autoFix = createAutoFix({
|
||||||
|
pluginConfigService: null as any,
|
||||||
|
accessService: null as any,
|
||||||
|
storageService: null as any,
|
||||||
|
sysSettingsService: {
|
||||||
|
async getPublicSettings() {
|
||||||
|
return {
|
||||||
|
oauthProviders: {
|
||||||
|
clogin: {
|
||||||
|
addonId: 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
},
|
||||||
|
oauthBoundService: {
|
||||||
|
async transaction(callback: any) {
|
||||||
|
return await callback({
|
||||||
|
async findOne() {
|
||||||
|
return {
|
||||||
|
id: 1,
|
||||||
|
setting: JSON.stringify({
|
||||||
|
loginType: ["alipay", "github"],
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
},
|
||||||
|
async update() {
|
||||||
|
updateCalled = true;
|
||||||
|
return { affected: 0 };
|
||||||
|
},
|
||||||
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
await autoFix.fixOauthSubtypeBoundType();
|
||||||
|
|
||||||
|
assert.equal(updateCalled, false);
|
||||||
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
import { Inject, Provide, Scope, ScopeEnum } from "@midwayjs/core";
|
import { Inject, Provide, Scope, ScopeEnum } from "@midwayjs/core";
|
||||||
import { logger } from "@certd/basic";
|
import { logger } from "@certd/basic";
|
||||||
import { AccessService } from "@certd/lib-server";
|
import { AccessService, AddonEntity, SysSettingsService } from "@certd/lib-server";
|
||||||
import { isComm } from "@certd/plus-core";
|
import { isComm } from "@certd/plus-core";
|
||||||
import { PluginConfigService } from "../plugin/service/plugin-config-service.js";
|
import { PluginConfigService } from "../plugin/service/plugin-config-service.js";
|
||||||
import { StorageService } from "../pipeline/service/storage-service.js";
|
import { StorageService } from "../pipeline/service/storage-service.js";
|
||||||
|
import { OauthBoundService } from "../login/service/oauth-bound-service.js";
|
||||||
|
import { OauthBoundEntity } from "../login/entity/oauth-bound.js";
|
||||||
|
|
||||||
export function parseStorageValue(value?: string) {
|
export function parseStorageValue(value?: string) {
|
||||||
if (!value) {
|
if (!value) {
|
||||||
@@ -33,6 +35,10 @@ export function buildLegacyGoogleAccountConfigWhere(email: string) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function buildOauthBoundType(type: string, subtype?: string) {
|
||||||
|
return subtype ? `${type}:${subtype}` : type;
|
||||||
|
}
|
||||||
|
|
||||||
@Provide()
|
@Provide()
|
||||||
@Scope(ScopeEnum.Request, { allowDowngrade: true })
|
@Scope(ScopeEnum.Request, { allowDowngrade: true })
|
||||||
export class AutoFix {
|
export class AutoFix {
|
||||||
@@ -45,9 +51,66 @@ export class AutoFix {
|
|||||||
@Inject()
|
@Inject()
|
||||||
storageService: StorageService;
|
storageService: StorageService;
|
||||||
|
|
||||||
|
@Inject()
|
||||||
|
sysSettingsService: SysSettingsService;
|
||||||
|
|
||||||
|
@Inject()
|
||||||
|
oauthBoundService: OauthBoundService;
|
||||||
|
|
||||||
async init() {
|
async init() {
|
||||||
await this.fixGoogleCommonEabAccountKey();
|
await this.fixGoogleCommonEabAccountKey();
|
||||||
|
await this.fixOauthSubtypeBoundType();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fixOauthSubtypeBoundType() {
|
||||||
|
try {
|
||||||
|
const publicSettings = await this.sysSettingsService.getPublicSettings();
|
||||||
|
const oauthProviders = publicSettings.oauthProviders || {};
|
||||||
|
await this.oauthBoundService.transaction(async manager => {
|
||||||
|
for (const [type, provider] of Object.entries(oauthProviders)) {
|
||||||
|
if (!provider.addonId) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const addonEntity = await manager.findOne(AddonEntity, { where: { id: provider.addonId } });
|
||||||
|
const legacyLoginType = this.getLegacyAddonLoginType(addonEntity?.setting);
|
||||||
|
if (!legacyLoginType) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const newType = buildOauthBoundType(type, legacyLoginType);
|
||||||
|
const res = await manager.update(OauthBoundEntity, { type }, { type: newType });
|
||||||
|
if (res.affected) {
|
||||||
|
logger.info(`已修复OAuth绑定历史数据,${type} -> ${newType},数量=${res.affected}`);
|
||||||
|
}
|
||||||
|
await this.convertLegacyAddonLoginTypeToArray(addonEntity, legacyLoginType, manager);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (e: any) {
|
||||||
|
logger.error("修复OAuth subtype绑定历史数据失败", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private getLegacyAddonLoginType(settingValue?: string) {
|
||||||
|
if (!settingValue) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const setting = JSON.parse(settingValue);
|
||||||
|
return typeof setting.loginType === "string" && setting.loginType ? setting.loginType : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async convertLegacyAddonLoginTypeToArray(addonEntity: AddonEntity | null, loginType: string, manager: any) {
|
||||||
|
if (!addonEntity?.setting) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const setting = JSON.parse(addonEntity.setting);
|
||||||
|
if (typeof setting.loginType !== "string") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setting.loginType = [loginType];
|
||||||
|
await manager.update(AddonEntity, { id: addonEntity.id }, { setting: JSON.stringify(setting) });
|
||||||
|
}
|
||||||
|
|
||||||
async fixGoogleCommonEabAccountKey() {
|
async fixGoogleCommonEabAccountKey() {
|
||||||
if (!isComm()) {
|
if (!isComm()) {
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { logger } from '@certd/basic';
|
import { logger } from '@certd/basic';
|
||||||
import { PlusService, SysInstallInfo, SysPrivateSettings, SysSettingsService } from '@certd/lib-server';
|
import { EncryptService, PlusService, SysInstallInfo, SysPrivateSettings, SysSettingsService } from '@certd/lib-server';
|
||||||
import { Config, Inject, Provide, Scope, ScopeEnum } from '@midwayjs/core';
|
import { Config, Inject, Provide, Scope, ScopeEnum } from '@midwayjs/core';
|
||||||
import crypto from 'crypto';
|
import crypto from 'crypto';
|
||||||
import { nanoid } from 'nanoid';
|
import { nanoid } from 'nanoid';
|
||||||
@@ -22,6 +22,10 @@ export class AutoInitSite {
|
|||||||
@Inject()
|
@Inject()
|
||||||
safeService: SafeService;
|
safeService: SafeService;
|
||||||
|
|
||||||
|
@Inject()
|
||||||
|
encryptService: EncryptService;
|
||||||
|
|
||||||
|
|
||||||
async init() {
|
async init() {
|
||||||
logger.info('初始化站点开始');
|
logger.info('初始化站点开始');
|
||||||
await this.startOptimizeDb();
|
await this.startOptimizeDb();
|
||||||
@@ -50,6 +54,8 @@ export class AutoInitSite {
|
|||||||
//加载一次密钥
|
//加载一次密钥
|
||||||
await this.sysSettingsService.getSecret();
|
await this.sysSettingsService.getSecret();
|
||||||
|
|
||||||
|
//初始化加密服务
|
||||||
|
await this.encryptService.doInit();
|
||||||
|
|
||||||
// 授权许可
|
// 授权许可
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -0,0 +1,51 @@
|
|||||||
|
/// <reference types="mocha" />
|
||||||
|
|
||||||
|
import assert from "node:assert/strict";
|
||||||
|
import { UserService } from "./user-service.js";
|
||||||
|
|
||||||
|
describe("UserService.initPassword", () => {
|
||||||
|
function createService(user: any) {
|
||||||
|
const service = new UserService();
|
||||||
|
service.info = async () => user;
|
||||||
|
let updatedParam: any;
|
||||||
|
service.update = async (param: any) => {
|
||||||
|
updatedParam = param;
|
||||||
|
};
|
||||||
|
return { service, getUpdatedParam: () => updatedParam };
|
||||||
|
}
|
||||||
|
|
||||||
|
it("sets a new password when current password is changeme", async () => {
|
||||||
|
const { service, getUpdatedParam } = createService({
|
||||||
|
id: 12,
|
||||||
|
password: "changeme",
|
||||||
|
passwordVersion: 2,
|
||||||
|
});
|
||||||
|
|
||||||
|
await service.initPassword(12, {
|
||||||
|
newPassword: "new-password",
|
||||||
|
confirmNewPassword: "new-password",
|
||||||
|
});
|
||||||
|
|
||||||
|
assert.deepEqual(getUpdatedParam(), {
|
||||||
|
id: 12,
|
||||||
|
password: "new-password",
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("rejects initPassword after password has already been set", async () => {
|
||||||
|
const { service } = createService({
|
||||||
|
id: 12,
|
||||||
|
password: "$2a$10$already-hashed",
|
||||||
|
passwordVersion: 2,
|
||||||
|
});
|
||||||
|
|
||||||
|
await assert.rejects(
|
||||||
|
() =>
|
||||||
|
service.initPassword(12, {
|
||||||
|
newPassword: "new-password",
|
||||||
|
confirmNewPassword: "new-password",
|
||||||
|
}),
|
||||||
|
/当前账号已设置密码/
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -1,22 +1,22 @@
|
|||||||
import { Inject, Provide, Scope, ScopeEnum } from '@midwayjs/core';
|
import { Inject, Provide, Scope, ScopeEnum } from "@midwayjs/core";
|
||||||
import { InjectEntityModel } from '@midwayjs/typeorm';
|
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 { UserEntity } from "../entity/user.js";
|
||||||
import * as _ from 'lodash-es';
|
import * as _ from "lodash-es";
|
||||||
import { BaseService, CommonException, Constants, FileService, SysInstallInfo, SysSettingsService } from '@certd/lib-server';
|
import { BaseService, CommonException, Constants, FileService, SysInstallInfo, SysSettingsService } from "@certd/lib-server";
|
||||||
import { RoleService } from './role-service.js';
|
import { RoleService } from "./role-service.js";
|
||||||
import { PermissionService } from './permission-service.js';
|
import { PermissionService } from "./permission-service.js";
|
||||||
import { UserRoleService } from './user-role-service.js';
|
import { UserRoleService } from "./user-role-service.js";
|
||||||
import { UserRoleEntity } from '../entity/user-role.js';
|
import { UserRoleEntity } from "../entity/user-role.js";
|
||||||
import bcrypt from 'bcryptjs';
|
import bcrypt from "bcryptjs";
|
||||||
import { RandomUtil } from '../../../../utils/random.js';
|
import { RandomUtil } from "../../../../utils/random.js";
|
||||||
import dayjs from 'dayjs';
|
import dayjs from "dayjs";
|
||||||
import { DbAdapter } from '../../../db/index.js';
|
import { DbAdapter } from "../../../db/index.js";
|
||||||
import { simpleNanoId, utils } from '@certd/basic';
|
import { simpleNanoId, utils } from "@certd/basic";
|
||||||
import { OauthBoundService } from '../../../login/service/oauth-bound-service.js';
|
import { OauthBoundService } from "../../../login/service/oauth-bound-service.js";
|
||||||
|
|
||||||
export type RegisterType = 'username' | 'mobile' | 'email';
|
export type RegisterType = "username" | "mobile" | "email";
|
||||||
export type ForgotPasswordType = 'mobile' | 'email';
|
export type ForgotPasswordType = "mobile" | "email";
|
||||||
|
|
||||||
export const AdminRoleId = 1;
|
export const AdminRoleId = 1;
|
||||||
|
|
||||||
@@ -83,14 +83,14 @@ export class UserService extends BaseService<UserEntity> {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
if (!_.isEmpty(exists)) {
|
if (!_.isEmpty(exists)) {
|
||||||
throw new CommonException('用户名已经存在');
|
throw new CommonException("用户名已经存在");
|
||||||
}
|
}
|
||||||
const plainPassword = param.password ?? RandomUtil.randomStr(6);
|
const plainPassword = param.password ?? RandomUtil.randomStr(6);
|
||||||
param.passwordVersion = 2;
|
param.passwordVersion = 2;
|
||||||
param.password = await this.genPassword(plainPassword, param.passwordVersion); // 默认密码 建议未改密码不能登陆
|
param.password = await this.genPassword(plainPassword, param.passwordVersion); // 默认密码 建议未改密码不能登陆
|
||||||
|
|
||||||
if (param.avatar) {
|
if (param.avatar) {
|
||||||
param.avatar = await this.fileService.saveFile(0, param.avatar, 'public');
|
param.avatar = await this.fileService.saveFile(0, param.avatar, "public");
|
||||||
}
|
}
|
||||||
|
|
||||||
await super.add(param);
|
await super.add(param);
|
||||||
@@ -107,13 +107,13 @@ export class UserService extends BaseService<UserEntity> {
|
|||||||
*/
|
*/
|
||||||
async update(param) {
|
async update(param) {
|
||||||
if (param.id == null) {
|
if (param.id == null) {
|
||||||
throw new CommonException('id不能为空');
|
throw new CommonException("id不能为空");
|
||||||
}
|
}
|
||||||
const userInfo = await this.repository.findOne({
|
const userInfo = await this.repository.findOne({
|
||||||
where: { id: param.id },
|
where: { id: param.id },
|
||||||
});
|
});
|
||||||
if (!userInfo) {
|
if (!userInfo) {
|
||||||
throw new CommonException('用户不存在');
|
throw new CommonException("用户不存在");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (param.username) {
|
if (param.username) {
|
||||||
@@ -125,7 +125,7 @@ export class UserService extends BaseService<UserEntity> {
|
|||||||
{ email: username, id: Not(id) },
|
{ email: username, id: Not(id) },
|
||||||
]);
|
]);
|
||||||
if (old != null) {
|
if (old != null) {
|
||||||
throw new CommonException('用户名已被占用');
|
throw new CommonException("用户名已被占用");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!_.isEmpty(param.password)) {
|
if (!_.isEmpty(param.password)) {
|
||||||
@@ -136,7 +136,7 @@ export class UserService extends BaseService<UserEntity> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (param.avatar) {
|
if (param.avatar) {
|
||||||
param.avatar = await this.fileService.saveFile(userInfo.id, param.avatar, 'public');
|
param.avatar = await this.fileService.saveFile(userInfo.id, param.avatar, "public");
|
||||||
}
|
}
|
||||||
await super.update(param);
|
await super.update(param);
|
||||||
await this.roleService.updateRoles(param.id, param.roles);
|
await this.roleService.updateRoles(param.id, param.roles);
|
||||||
@@ -168,7 +168,7 @@ export class UserService extends BaseService<UserEntity> {
|
|||||||
async buildPlainPassword(rawPassword: string) {
|
async buildPlainPassword(rawPassword: string) {
|
||||||
const setting: SysInstallInfo = await this.sysSettingsService.getSetting(SysInstallInfo);
|
const setting: SysInstallInfo = await this.sysSettingsService.getSetting(SysInstallInfo);
|
||||||
if (!setting.siteId) {
|
if (!setting.siteId) {
|
||||||
throw new CommonException('站点ID还未初始化');
|
throw new CommonException("站点ID还未初始化");
|
||||||
}
|
}
|
||||||
const prefixSiteId = setting.siteId.substring(1, 5);
|
const prefixSiteId = setting.siteId.substring(1, 5);
|
||||||
return rawPassword + prefixSiteId;
|
return rawPassword + prefixSiteId;
|
||||||
@@ -184,54 +184,55 @@ export class UserService extends BaseService<UserEntity> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async register(type: string, user: UserEntity, withTx?: (tx: EntityManager) => Promise<void>) {
|
async register(type: string, user: UserEntity, withTx?: (tx: EntityManager) => Promise<void>) {
|
||||||
if (!user.password) {
|
// if (!user.password) {
|
||||||
user.password = simpleNanoId();
|
// user.password = simpleNanoId();
|
||||||
}
|
// }
|
||||||
|
|
||||||
if (user.username) {
|
if (user.username) {
|
||||||
const username = user.username;
|
const username = user.username;
|
||||||
const old = await this.findOne([{ username: username }, { mobile: username }, { email: username }]);
|
const old = await this.findOne([{ username: username }, { mobile: username }, { email: username }]);
|
||||||
if (old != null) {
|
if (old != null) {
|
||||||
throw new CommonException('用户名已被注册');
|
throw new CommonException("用户名已被注册");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (user.mobile) {
|
if (user.mobile) {
|
||||||
const mobile = user.mobile;
|
const mobile = user.mobile;
|
||||||
|
|
||||||
user.nickName = user.username || mobile.substring(0, 3) + '****' + mobile.substring(7);
|
user.nickName = user.username || mobile.substring(0, 3) + "****" + mobile.substring(7);
|
||||||
const old = await this.findOne([{ username: mobile }, { mobile: mobile }, { email: mobile }]);
|
const old = await this.findOne([{ username: mobile }, { mobile: mobile }, { email: mobile }]);
|
||||||
if (old != null) {
|
if (old != null) {
|
||||||
throw new CommonException('手机号已被注册');
|
throw new CommonException("手机号已被注册");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (user.email) {
|
if (user.email) {
|
||||||
const email = user.email;
|
const email = user.email;
|
||||||
const old = await this.findOne([{ username: email }, { mobile: email }, { email: email }]);
|
const old = await this.findOne([{ username: email }, { mobile: email }, { email: email }]);
|
||||||
if (old != null) {
|
if (old != null) {
|
||||||
throw new CommonException('邮箱已被注册');
|
throw new CommonException("邮箱已被注册");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!user.username) {
|
if (!user.username) {
|
||||||
user.username = 'user_' + simpleNanoId();
|
user.username = "user_" + simpleNanoId();
|
||||||
}
|
}
|
||||||
|
|
||||||
let newUser: UserEntity = UserEntity.of({
|
let newUser: UserEntity = UserEntity.of({
|
||||||
username: user.username,
|
username: user.username,
|
||||||
password: user.password,
|
password: user.password,
|
||||||
email: user.email || '',
|
email: user.email || "",
|
||||||
mobile: user.mobile || '',
|
mobile: user.mobile || "",
|
||||||
nickName: user.nickName || user.username,
|
nickName: user.nickName || user.username,
|
||||||
avatar: user.avatar || '',
|
avatar: user.avatar || "",
|
||||||
phoneCode: user.phoneCode || '86',
|
phoneCode: user.phoneCode || "86",
|
||||||
status: 1,
|
status: 1,
|
||||||
passwordVersion: 2,
|
passwordVersion: 2,
|
||||||
});
|
});
|
||||||
if (!newUser.password) {
|
if (!newUser.password) {
|
||||||
newUser.password = RandomUtil.randomStr(6);
|
newUser.password = "changeme";
|
||||||
}
|
} else {
|
||||||
newUser.password = await this.genPassword(newUser.password, newUser.passwordVersion);
|
newUser.password = await this.genPassword(newUser.password, newUser.passwordVersion);
|
||||||
|
}
|
||||||
|
|
||||||
await this.transaction(async txManager => {
|
await this.transaction(async txManager => {
|
||||||
newUser = await txManager.save(newUser);
|
newUser = await txManager.save(newUser);
|
||||||
@@ -246,28 +247,28 @@ export class UserService extends BaseService<UserEntity> {
|
|||||||
|
|
||||||
delete newUser.password;
|
delete newUser.password;
|
||||||
|
|
||||||
utils.mitter.emit('register', { userId: newUser.id });
|
utils.mitter.emit("register", { userId: newUser.id });
|
||||||
|
|
||||||
return newUser;
|
return newUser;
|
||||||
}
|
}
|
||||||
|
|
||||||
async forgotPassword(data: { type: ForgotPasswordType; input?: string; phoneCode?: string; validateCode: string; password: string; confirmPassword: string }) {
|
async forgotPassword(data: { type: ForgotPasswordType; input?: string; phoneCode?: string; validateCode: string; password: string; confirmPassword: string }) {
|
||||||
if (!data.type) {
|
if (!data.type) {
|
||||||
throw new CommonException('找回类型不能为空');
|
throw new CommonException("找回类型不能为空");
|
||||||
}
|
}
|
||||||
if (data.password !== data.confirmPassword) {
|
if (data.password !== data.confirmPassword) {
|
||||||
throw new CommonException('两次输入的密码不一致');
|
throw new CommonException("两次输入的密码不一致");
|
||||||
}
|
}
|
||||||
const where: any = {
|
const where: any = {
|
||||||
[data.type]: data.input,
|
[data.type]: data.input,
|
||||||
};
|
};
|
||||||
if (data.type === 'mobile') {
|
if (data.type === "mobile") {
|
||||||
where.phoneCode = data.phoneCode ?? '86';
|
where.phoneCode = data.phoneCode ?? "86";
|
||||||
}
|
}
|
||||||
const user = await this.findOne({ [data.type]: data.input });
|
const user = await this.findOne({ [data.type]: data.input });
|
||||||
console.log('user', user);
|
console.log("user", user);
|
||||||
if (!user) {
|
if (!user) {
|
||||||
throw new CommonException('用户不存在');
|
throw new CommonException("用户不存在");
|
||||||
// return;
|
// return;
|
||||||
}
|
}
|
||||||
await this.resetPassword(user.id, data.password);
|
await this.resetPassword(user.id, data.password);
|
||||||
@@ -282,7 +283,7 @@ export class UserService extends BaseService<UserEntity> {
|
|||||||
const user = await this.info(userId);
|
const user = await this.info(userId);
|
||||||
const passwordChecked = await this.checkPassword(form.password, user.password, user.passwordVersion);
|
const passwordChecked = await this.checkPassword(form.password, user.password, user.passwordVersion);
|
||||||
if (!passwordChecked) {
|
if (!passwordChecked) {
|
||||||
throw new CommonException('原密码错误');
|
throw new CommonException("原密码错误");
|
||||||
}
|
}
|
||||||
const param = {
|
const param = {
|
||||||
id: userId,
|
id: userId,
|
||||||
@@ -292,9 +293,26 @@ export class UserService extends BaseService<UserEntity> {
|
|||||||
await this.update(param);
|
await this.update(param);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async initPassword(userId: any, form: any) {
|
||||||
|
const user = await this.info(userId);
|
||||||
|
if (user.password !== "changeme") {
|
||||||
|
throw new CommonException("当前账号已设置密码");
|
||||||
|
}
|
||||||
|
if (!form.newPassword) {
|
||||||
|
throw new CommonException("新密码不能为空");
|
||||||
|
}
|
||||||
|
if (form.newPassword !== form.confirmNewPassword) {
|
||||||
|
throw new CommonException("两次输入的密码不一致");
|
||||||
|
}
|
||||||
|
await this.update({
|
||||||
|
id: userId,
|
||||||
|
password: form.newPassword,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
async resetPassword(userId: any, newPasswd: string) {
|
async resetPassword(userId: any, newPasswd: string) {
|
||||||
if (!userId) {
|
if (!userId) {
|
||||||
throw new CommonException('userId不能为空');
|
throw new CommonException("userId不能为空");
|
||||||
}
|
}
|
||||||
const param = {
|
const param = {
|
||||||
id: userId,
|
id: userId,
|
||||||
@@ -305,15 +323,15 @@ export class UserService extends BaseService<UserEntity> {
|
|||||||
|
|
||||||
//@ts-ignore
|
//@ts-ignore
|
||||||
async delete(ids: any) {
|
async delete(ids: any) {
|
||||||
if (typeof ids === 'string') {
|
if (typeof ids === "string") {
|
||||||
ids = ids.split(',');
|
ids = ids.split(",");
|
||||||
ids = ids.map(id => parseInt(id));
|
ids = ids.map(id => parseInt(id));
|
||||||
}
|
}
|
||||||
if (ids.length === 0) {
|
if (ids.length === 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (ids.includes(1)) {
|
if (ids.includes(1)) {
|
||||||
throw new CommonException('不能删除管理员');
|
throw new CommonException("不能删除管理员");
|
||||||
}
|
}
|
||||||
await super.delete(ids);
|
await super.delete(ids);
|
||||||
await this.oauthBoundService.deleteWhere({
|
await this.oauthBoundService.deleteWhere({
|
||||||
@@ -323,7 +341,7 @@ export class UserService extends BaseService<UserEntity> {
|
|||||||
|
|
||||||
async isAdmin(userId: any) {
|
async isAdmin(userId: any) {
|
||||||
if (!userId) {
|
if (!userId) {
|
||||||
throw new CommonException('userId不能为空');
|
throw new CommonException("userId不能为空");
|
||||||
}
|
}
|
||||||
const userRoles = await this.userRoleService.find({
|
const userRoles = await this.userRoleService.find({
|
||||||
where: {
|
where: {
|
||||||
@@ -338,7 +356,7 @@ export class UserService extends BaseService<UserEntity> {
|
|||||||
|
|
||||||
async updateStatus(id: number, status: number) {
|
async updateStatus(id: number, status: number) {
|
||||||
if (!id) {
|
if (!id) {
|
||||||
throw new CommonException('userId不能为空');
|
throw new CommonException("userId不能为空");
|
||||||
}
|
}
|
||||||
await this.repository.update(id, {
|
await this.repository.update(id, {
|
||||||
status,
|
status,
|
||||||
@@ -355,16 +373,16 @@ export class UserService extends BaseService<UserEntity> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async registerCountPerDay(param: { days: number } = { days: 7 }) {
|
async registerCountPerDay(param: { days: number } = { days: 7 }) {
|
||||||
const todayEnd = dayjs().endOf('day');
|
const todayEnd = dayjs().endOf("day");
|
||||||
const result = await this.getRepository()
|
const result = await this.getRepository()
|
||||||
.createQueryBuilder('main')
|
.createQueryBuilder("main")
|
||||||
.select(`${this.dbAdapter.date('main.createTime')} AS date`) // 将UNIX时间戳转换为日期
|
.select(`${this.dbAdapter.date("main.createTime")} AS date`) // 将UNIX时间戳转换为日期
|
||||||
.addSelect('COUNT(1) AS count')
|
.addSelect("COUNT(1) AS count")
|
||||||
.where({
|
.where({
|
||||||
// 0点
|
// 0点
|
||||||
createTime: MoreThan(todayEnd.add(-param.days, 'day').toDate()),
|
createTime: MoreThan(todayEnd.add(-param.days, "day").toDate()),
|
||||||
})
|
})
|
||||||
.groupBy('date')
|
.groupBy("date")
|
||||||
.getRawMany();
|
.getRawMany();
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
@@ -384,7 +402,7 @@ export class UserService extends BaseService<UserEntity> {
|
|||||||
status: 1,
|
status: 1,
|
||||||
},
|
},
|
||||||
order: {
|
order: {
|
||||||
updateTime: 'DESC',
|
updateTime: "DESC",
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -396,61 +414,61 @@ export class UserService extends BaseService<UserEntity> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async verifyIdentity(userId: number, body: { identityType: 'password' | 'email' | 'mobile'; identityPassword?: string; identityValidateCode?: string }, codeService: any) {
|
async verifyIdentity(userId: number, body: { identityType: "password" | "email" | "mobile"; identityPassword?: string; identityValidateCode?: string }, codeService: any) {
|
||||||
const user = await this.info(userId);
|
const user = await this.info(userId);
|
||||||
if (body.identityType === 'password') {
|
if (body.identityType === "password") {
|
||||||
const passwordChecked = await this.checkPassword(body.identityPassword, user.password, user.passwordVersion);
|
const passwordChecked = await this.checkPassword(body.identityPassword, user.password, user.passwordVersion);
|
||||||
if (!passwordChecked) {
|
if (!passwordChecked) {
|
||||||
throw new CommonException('密码错误');
|
throw new CommonException("密码错误");
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (body.identityType === 'email') {
|
if (body.identityType === "email") {
|
||||||
if (!user.email) {
|
if (!user.email) {
|
||||||
throw new CommonException('当前账号未绑定邮箱');
|
throw new CommonException("当前账号未绑定邮箱");
|
||||||
}
|
}
|
||||||
codeService.checkEmailCode({
|
codeService.checkEmailCode({
|
||||||
email: user.email,
|
email: user.email,
|
||||||
validateCode: body.identityValidateCode,
|
validateCode: body.identityValidateCode,
|
||||||
verificationType: 'contactIdentity',
|
verificationType: "contactIdentity",
|
||||||
throwError: true,
|
throwError: true,
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (body.identityType === 'mobile') {
|
if (body.identityType === "mobile") {
|
||||||
if (!user.mobile) {
|
if (!user.mobile) {
|
||||||
throw new CommonException('当前账号未绑定手机号');
|
throw new CommonException("当前账号未绑定手机号");
|
||||||
}
|
}
|
||||||
await codeService.checkSmsCode({
|
await codeService.checkSmsCode({
|
||||||
mobile: user.mobile,
|
mobile: user.mobile,
|
||||||
phoneCode: user.phoneCode || '86',
|
phoneCode: user.phoneCode || "86",
|
||||||
smsCode: body.identityValidateCode,
|
smsCode: body.identityValidateCode,
|
||||||
verificationType: 'contactIdentity',
|
verificationType: "contactIdentity",
|
||||||
throwError: true,
|
throwError: true,
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
throw new CommonException('不支持的验证方式');
|
throw new CommonException("不支持的验证方式");
|
||||||
}
|
}
|
||||||
|
|
||||||
checkContactIdentityValidation(userId: number, validationCode: string, codeService: any) {
|
checkContactIdentityValidation(userId: number, validationCode: string, codeService: any) {
|
||||||
const validationValue = codeService.getValidationValue(validationCode);
|
const validationValue = codeService.getValidationValue(validationCode);
|
||||||
if (!validationValue || validationValue.type !== 'contactIdentity' || validationValue.userId !== userId) {
|
if (!validationValue || validationValue.type !== "contactIdentity" || validationValue.userId !== userId) {
|
||||||
throw new CommonException('请先验证本人操作');
|
throw new CommonException("请先验证本人操作");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async updateMobile(userId: number, body: { phoneCode?: string; mobile: string }) {
|
async updateMobile(userId: number, body: { phoneCode?: string; mobile: string }) {
|
||||||
const mobile = body.mobile?.trim();
|
const mobile = body.mobile?.trim();
|
||||||
if (!mobile) {
|
if (!mobile) {
|
||||||
throw new CommonException('手机号不能为空');
|
throw new CommonException("手机号不能为空");
|
||||||
}
|
}
|
||||||
const old = await this.findOne(buildUserContactConflictWhere(mobile, userId));
|
const old = await this.findOne(buildUserContactConflictWhere(mobile, userId));
|
||||||
if (old != null) {
|
if (old != null) {
|
||||||
throw new CommonException('手机号已被占用');
|
throw new CommonException("手机号已被占用");
|
||||||
}
|
}
|
||||||
await this.repository.update(userId, {
|
await this.repository.update(userId, {
|
||||||
phoneCode: body.phoneCode || '86',
|
phoneCode: body.phoneCode || "86",
|
||||||
mobile,
|
mobile,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -458,11 +476,11 @@ export class UserService extends BaseService<UserEntity> {
|
|||||||
async updateEmail(userId: number, body: { email: string }) {
|
async updateEmail(userId: number, body: { email: string }) {
|
||||||
const email = body.email?.trim();
|
const email = body.email?.trim();
|
||||||
if (!email) {
|
if (!email) {
|
||||||
throw new CommonException('邮箱不能为空');
|
throw new CommonException("邮箱不能为空");
|
||||||
}
|
}
|
||||||
const old = await this.findOne(buildUserContactConflictWhere(email, userId));
|
const old = await this.findOne(buildUserContactConflictWhere(email, userId));
|
||||||
if (old != null) {
|
if (old != null) {
|
||||||
throw new CommonException('邮箱已被占用');
|
throw new CommonException("邮箱已被占用");
|
||||||
}
|
}
|
||||||
await this.repository.update(userId, {
|
await this.repository.update(userId, {
|
||||||
email,
|
email,
|
||||||
@@ -471,7 +489,7 @@ export class UserService extends BaseService<UserEntity> {
|
|||||||
|
|
||||||
async getAllUserIds() {
|
async getAllUserIds() {
|
||||||
const users = await this.repository.find({
|
const users = await this.repository.find({
|
||||||
select: ['id'],
|
select: ["id"],
|
||||||
where: {
|
where: {
|
||||||
status: 1,
|
status: 1,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -41,9 +41,12 @@ export type BuildLoginUrlReq = {
|
|||||||
redirectUri: string;
|
redirectUri: string;
|
||||||
forType?: string;
|
forType?: string;
|
||||||
from?:string;
|
from?:string;
|
||||||
|
subtype?: string;
|
||||||
|
state?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type BuildLogoutUrlReq = {
|
export type BuildLogoutUrlReq = {
|
||||||
|
subtype?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type LogoutUrlReply = {
|
export type LogoutUrlReply = {
|
||||||
|
|||||||
@@ -0,0 +1,13 @@
|
|||||||
|
export const IconSets = [
|
||||||
|
"streamline-logos",
|
||||||
|
"logos",
|
||||||
|
"fa-brands",
|
||||||
|
"fa-solid",
|
||||||
|
"fa-regular",
|
||||||
|
"carbon",
|
||||||
|
"ion",
|
||||||
|
"ant-design",
|
||||||
|
"mdi",
|
||||||
|
"twemoji",
|
||||||
|
"svg-spinners"
|
||||||
|
]
|
||||||
@@ -1,6 +1,32 @@
|
|||||||
import { AddonInput, BaseAddon, IsAddon } from "@certd/lib-server";
|
import { AddonInput, BaseAddon, IsAddon } from "@certd/lib-server";
|
||||||
import { BuildLoginUrlReq, BuildLogoutUrlReq, IOauthProvider, OnCallbackReq } from "../api.js";
|
import { BuildLoginUrlReq, BuildLogoutUrlReq, IOauthProvider, OnCallbackReq } from "../api.js";
|
||||||
|
|
||||||
|
const CLOGIN_TYPES = [
|
||||||
|
{ label: "QQ", value: "qq", icon: "cib:tencent-qq:#007AFF" },
|
||||||
|
{ label: "微信", value: "wx", icon: "simple-icons:wechat:#34C759" },
|
||||||
|
{ label: "支付宝", value: "alipay", icon: "simple-icons:alipay:#0099ff" },
|
||||||
|
{ label: "微博", value: "sina", icon: "uiw:weibo:#FF3B30" },
|
||||||
|
{ label: "百度", value: "baidu", icon: "simple-icons:baidu:#007AFF" },
|
||||||
|
{ label: "华为", value: "huawei", icon: "simple-icons:huawei:#ff0000" },
|
||||||
|
{ label: "小米", value: "xiaomi", icon: "simple-icons:xiaomi:#FF9500" },
|
||||||
|
{ label: "谷歌", value: "google", icon: "flat-color-icons:google" },
|
||||||
|
{ label: "微软", value: "microsoft", icon: "logos:microsoft-icon" },
|
||||||
|
{ label: "Facebook", value: "facebook", icon: "logos:facebook" },
|
||||||
|
{ label: "Twitter", value: "twitter", icon: "logos:twitter" },
|
||||||
|
{ label: "钉钉", value: "dingtalk", icon: "ant-design:dingding-outlined:#007AFF" },
|
||||||
|
{ label: "Gitee", value: "gitee", icon: "simple-icons:gitee:#c71d23" },
|
||||||
|
{ label: "Github", value: "github", icon: "logos:github-icon" },
|
||||||
|
];
|
||||||
|
|
||||||
|
function getCloginType(subtype?: string, loginType?: string | string[]) {
|
||||||
|
const types = Array.isArray(loginType) ? loginType : [loginType];
|
||||||
|
const type = subtype || types.find(item => !!item);
|
||||||
|
if (!type) {
|
||||||
|
throw new Error("请选择彩虹聚合登录类型");
|
||||||
|
}
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
|
||||||
@IsAddon({
|
@IsAddon({
|
||||||
addonType: "oauth",
|
addonType: "oauth",
|
||||||
name: 'clogin',
|
name: 'clogin',
|
||||||
@@ -21,37 +47,27 @@ export class CloginOauthProvider extends BaseAddon implements IOauthProvider {
|
|||||||
@AddonInput({
|
@AddonInput({
|
||||||
title: "登录类型",
|
title: "登录类型",
|
||||||
component: {
|
component: {
|
||||||
name: "a-auto-complete",
|
name: "a-select",
|
||||||
options: [
|
vModel: "value",
|
||||||
{ label: "QQ", value: "qq" },
|
mode: "tags",
|
||||||
{ label: "微信", value: "wx" },
|
multiple: true,
|
||||||
{ label: "支付宝", value: "alipay" },
|
options: CLOGIN_TYPES,
|
||||||
{ label: "微博", value: "sina" },
|
|
||||||
{ label: "百度", value: "baidu" },
|
|
||||||
{ label: "华为", value: "huawei" },
|
|
||||||
{ label: "小米", value: "xiaomi" },
|
|
||||||
{ label: "谷歌", value: "google" },
|
|
||||||
{ label: "微软", value: "microsoft" },
|
|
||||||
{ label: "Facebook", value: "facebook" },
|
|
||||||
{ label: "Twitter", value: "twitter" },
|
|
||||||
{ label: "钉钉", value: "dingtalk" },
|
|
||||||
{ label: "Gitee", value: "gitee" },
|
|
||||||
{ label: "Github", value: "github" },
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
required: true,
|
required: true,
|
||||||
})
|
})
|
||||||
loginType = "";
|
loginType: string[] | string = [];
|
||||||
|
|
||||||
@AddonInput({
|
get types() {
|
||||||
title: "自定义图标",
|
const loginTypes = Array.isArray(this.loginType) ? this.loginType : [this.loginType].filter(Boolean);
|
||||||
component: {
|
return loginTypes.map(type => {
|
||||||
name:"fs-icon-selector",
|
const option = CLOGIN_TYPES.find(item => item.value === type);
|
||||||
vModel:"modelValue"
|
return {
|
||||||
},
|
type,
|
||||||
required: false,
|
name: option?.label || type,
|
||||||
})
|
icon: option?.icon,
|
||||||
icon = "";
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
@AddonInput({
|
@AddonInput({
|
||||||
title: "AppId",
|
title: "AppId",
|
||||||
@@ -73,20 +89,19 @@ export class CloginOauthProvider extends BaseAddon implements IOauthProvider {
|
|||||||
async buildLoginUrl(params: BuildLoginUrlReq) {
|
async buildLoginUrl(params: BuildLoginUrlReq) {
|
||||||
|
|
||||||
let redirectUri = params.redirectUri || ""
|
let redirectUri = params.redirectUri || ""
|
||||||
if(redirectUri.indexOf("localhost:3008")>=0){
|
const loginType = getCloginType(params.subtype, this.loginType);
|
||||||
redirectUri = redirectUri.replace("localhost:3008", "certd.handfree.work")
|
// if(redirectUri.indexOf("localhost:3008")>=0){
|
||||||
}
|
// redirectUri = redirectUri.replace("localhost:3008", "certd.handfree.work")
|
||||||
|
// }
|
||||||
const res = await this.ctx.http.request({
|
const res = await this.ctx.http.request({
|
||||||
url: `${this.endpoint}/connect.php?act=login&appid=${this.appId}&appkey=${this.appKey}&type=${this.loginType}&redirect_uri=${redirectUri}`
|
url: `${this.endpoint}/connect.php?act=login&appid=${this.appId}&appkey=${this.appKey}&type=${loginType}&redirect_uri=${redirectUri}&state=${params.state}`
|
||||||
})
|
})
|
||||||
|
|
||||||
this.checkRes(res)
|
this.checkRes(res)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
loginUrl: res.url,
|
loginUrl: res.url,
|
||||||
ticketValue: {
|
ticketValue: {},
|
||||||
state: "",
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -101,8 +116,9 @@ export class CloginOauthProvider extends BaseAddon implements IOauthProvider {
|
|||||||
//校验state
|
//校验state
|
||||||
|
|
||||||
const code = req.code || ""
|
const code = req.code || ""
|
||||||
|
const loginType = getCloginType(req.ticketValue?.subtype, this.loginType);
|
||||||
|
|
||||||
const tokenEndpoint = `http://clogin.yfy.docmirror.cn/connect.php?act=callback&appid=${this.appId}&appkey=${this.appKey}&type=${this.loginType}&code=${code}`
|
const tokenEndpoint = `${this.endpoint}/connect.php?act=callback&appid=${this.appId}&appkey=${this.appKey}&type=${loginType}&code=${code}`
|
||||||
const res = await this.ctx.utils.http.request({
|
const res = await this.ctx.utils.http.request({
|
||||||
url: tokenEndpoint,
|
url: tokenEndpoint,
|
||||||
method: "post",
|
method: "post",
|
||||||
|
|||||||
@@ -79,19 +79,13 @@ gitee.userInfo = https://gitee.com/api/v5/user
|
|||||||
async buildLoginUrl(params: BuildLoginUrlReq) {
|
async buildLoginUrl(params: BuildLoginUrlReq) {
|
||||||
|
|
||||||
let scope = "user_info" // Scope of the access request
|
let scope = "user_info" // Scope of the access request
|
||||||
let state:any = {
|
|
||||||
forType: params.forType || 'login',
|
|
||||||
}
|
|
||||||
state = this.ctx.utils.hash.base64(JSON.stringify(state))
|
|
||||||
|
|
||||||
const authorizeEndpoint = "https://gitee.com/oauth/authorize"
|
const authorizeEndpoint = "https://gitee.com/oauth/authorize"
|
||||||
const redirectUrl = encodeURIComponent(params.redirectUri)
|
const redirectUrl = encodeURIComponent(params.redirectUri)
|
||||||
// https://gitee.com/oauth/authorize?client_id=5bb5f4158af41c50c7a17b5d9068244e97d3ee572def6a57ed32fd8c9d760ad1&redirect_uri=http%3A%2F%2Fcasdoor.docmirror.cn%3A8000%2Fcallback&response_type=code
|
// https://gitee.com/oauth/authorize?client_id=5bb5f4158af41c50c7a17b5d9068244e97d3ee572def6a57ed32fd8c9d760ad1&redirect_uri=http%3A%2F%2Fcasdoor.docmirror.cn%3A8000%2Fcallback&response_type=code
|
||||||
const loginUrl = `${authorizeEndpoint}?client_id=${this.clientId}&redirect_uri=${redirectUrl}&response_type=code&scope=${scope}&state=${state}`
|
const loginUrl = `${authorizeEndpoint}?client_id=${this.clientId}&redirect_uri=${redirectUrl}&response_type=code&scope=${scope}&state=${params.state}`
|
||||||
return {
|
return {
|
||||||
loginUrl,
|
loginUrl,
|
||||||
ticketValue: {
|
ticketValue: {
|
||||||
state,
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,19 +30,12 @@ export class GithubOauthProvider extends BaseAddon implements IOauthProvider {
|
|||||||
async buildLoginUrl(params: BuildLoginUrlReq) {
|
async buildLoginUrl(params: BuildLoginUrlReq) {
|
||||||
|
|
||||||
let scope = "user:email" // Scope of the access request
|
let scope = "user:email" // Scope of the access request
|
||||||
let state:any = {
|
|
||||||
forType: params.forType || 'login',
|
|
||||||
}
|
|
||||||
state = this.ctx.utils.hash.base64(JSON.stringify(state))
|
|
||||||
|
|
||||||
const authorizeEndpoint = "https://github.com/login/oauth/authorize"
|
const authorizeEndpoint = "https://github.com/login/oauth/authorize"
|
||||||
const redirectUrl = encodeURIComponent(params.redirectUri)
|
const redirectUrl = encodeURIComponent(params.redirectUri)
|
||||||
const loginUrl = `${authorizeEndpoint}?client_id=${this.clientId}&redirect_uri=${redirectUrl}&response_type=code&scope=${scope}&state=${state}`
|
const loginUrl = `${authorizeEndpoint}?client_id=${this.clientId}&redirect_uri=${redirectUrl}&response_type=code&scope=${scope}&state=${params.state}`
|
||||||
return {
|
return {
|
||||||
loginUrl,
|
loginUrl,
|
||||||
ticketValue: {
|
ticketValue: { },
|
||||||
state,
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -30,18 +30,13 @@ export class GoogleOauthProvider extends BaseAddon implements IOauthProvider {
|
|||||||
async buildLoginUrl(params: BuildLoginUrlReq) {
|
async buildLoginUrl(params: BuildLoginUrlReq) {
|
||||||
|
|
||||||
let scope = "email profile" // Scope of the access request
|
let scope = "email profile" // Scope of the access request
|
||||||
let state:any = {
|
|
||||||
forType: params.forType || 'login',
|
|
||||||
}
|
|
||||||
state = this.ctx.utils.hash.base64(JSON.stringify(state))
|
|
||||||
|
|
||||||
const authorizeEndpoint = "https://accounts.google.com/o/oauth2/auth"
|
const authorizeEndpoint = "https://accounts.google.com/o/oauth2/auth"
|
||||||
const redirectUrl = encodeURIComponent(params.redirectUri)
|
const redirectUrl = encodeURIComponent(params.redirectUri)
|
||||||
const loginUrl = `${authorizeEndpoint}?client_id=${this.clientId}&redirect_uri=${redirectUrl}&response_type=code&scope=${scope}&state=${state}`
|
const loginUrl = `${authorizeEndpoint}?client_id=${this.clientId}&redirect_uri=${redirectUrl}&response_type=code&scope=${scope}&state=${params.state}`
|
||||||
return {
|
return {
|
||||||
loginUrl,
|
loginUrl,
|
||||||
ticketValue: {
|
ticketValue: {
|
||||||
state,
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -42,18 +42,12 @@ export class MicrosoftOauthProvider extends BaseAddon implements IOauthProvider
|
|||||||
async buildLoginUrl(params: BuildLoginUrlReq) {
|
async buildLoginUrl(params: BuildLoginUrlReq) {
|
||||||
|
|
||||||
let scope = "openid profile email User.Read" // Scope of the access request
|
let scope = "openid profile email User.Read" // Scope of the access request
|
||||||
let state:any = {
|
|
||||||
forType: params.forType || 'login',
|
|
||||||
}
|
|
||||||
state = this.ctx.utils.hash.base64(JSON.stringify(state))
|
|
||||||
|
|
||||||
const authorizeEndpoint = `https://login.microsoftonline.com/${this.tenantId}/oauth2/v2.0/authorize`
|
const authorizeEndpoint = `https://login.microsoftonline.com/${this.tenantId}/oauth2/v2.0/authorize`
|
||||||
const redirectUrl = encodeURIComponent(params.redirectUri)
|
const redirectUrl = encodeURIComponent(params.redirectUri)
|
||||||
const loginUrl = `${authorizeEndpoint}?client_id=${this.clientId}&redirect_uri=${redirectUrl}&response_type=code&scope=${scope}&state=${state}`
|
const loginUrl = `${authorizeEndpoint}?client_id=${this.clientId}&redirect_uri=${redirectUrl}&response_type=code&scope=${scope}&state=${params.state}`
|
||||||
return {
|
return {
|
||||||
loginUrl,
|
loginUrl,
|
||||||
ticketValue: {
|
ticketValue: {
|
||||||
state,
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { AddonInput, BaseAddon, IsAddon } from "@certd/lib-server";
|
import { AddonInput, BaseAddon, IsAddon } from "@certd/lib-server";
|
||||||
import { BuildLoginUrlReq, BuildLogoutUrlReq, IOauthProvider, OnCallbackReq } from "../api.js";
|
import { BuildLoginUrlReq, BuildLogoutUrlReq, IOauthProvider, OnCallbackReq } from "../api.js";
|
||||||
|
import { IconSets } from "../iconsets.js";
|
||||||
|
|
||||||
@IsAddon({
|
@IsAddon({
|
||||||
addonType: "oauth",
|
addonType: "oauth",
|
||||||
@@ -15,7 +16,8 @@ export class OidcOauthProvider extends BaseAddon implements IOauthProvider {
|
|||||||
title: "自定义图标",
|
title: "自定义图标",
|
||||||
component: {
|
component: {
|
||||||
name:"fs-icon-selector",
|
name:"fs-icon-selector",
|
||||||
vModel:"modelValue"
|
vModel:"modelValue",
|
||||||
|
iconSets: IconSets,
|
||||||
},
|
},
|
||||||
required: false,
|
required: false,
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -63,6 +63,7 @@ export class SynologyAccess extends BaseAccess {
|
|||||||
name: "a-switch",
|
name: "a-switch",
|
||||||
vModel: "checked",
|
vModel: "checked",
|
||||||
},
|
},
|
||||||
|
col:{span:24},
|
||||||
helper: "是否启用了双重认证",
|
helper: "是否启用了双重认证",
|
||||||
required: true,
|
required: true,
|
||||||
})
|
})
|
||||||
@@ -76,6 +77,7 @@ export class SynologyAccess extends BaseAccess {
|
|||||||
type: "access",
|
type: "access",
|
||||||
typeName: "synology",
|
typeName: "synology",
|
||||||
},
|
},
|
||||||
|
col:{span:24},
|
||||||
mergeScript: `
|
mergeScript: `
|
||||||
return {
|
return {
|
||||||
component:{
|
component:{
|
||||||
|
|||||||
@@ -23,3 +23,4 @@
|
|||||||
},
|
},
|
||||||
"exclude": ["*.js", "*.ts", "dist", "node_modules", "src/**/*.test.ts", "test"]
|
"exclude": ["*.js", "*.ts", "dist", "node_modules", "src/**/*.test.ts", "test"]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Generated
+97
-108
@@ -52,7 +52,7 @@ importers:
|
|||||||
packages/core/acme-client:
|
packages/core/acme-client:
|
||||||
dependencies:
|
dependencies:
|
||||||
'@certd/basic':
|
'@certd/basic':
|
||||||
specifier: ^1.39.12
|
specifier: ^1.39.16
|
||||||
version: link:../basic
|
version: link:../basic
|
||||||
'@peculiar/x509':
|
'@peculiar/x509':
|
||||||
specifier: ^1.11.0
|
specifier: ^1.11.0
|
||||||
@@ -234,10 +234,10 @@ importers:
|
|||||||
packages/core/pipeline:
|
packages/core/pipeline:
|
||||||
dependencies:
|
dependencies:
|
||||||
'@certd/basic':
|
'@certd/basic':
|
||||||
specifier: ^1.39.12
|
specifier: ^1.39.16
|
||||||
version: link:../basic
|
version: link:../basic
|
||||||
'@certd/plus-core':
|
'@certd/plus-core':
|
||||||
specifier: ^1.39.12
|
specifier: ^1.39.16
|
||||||
version: link:../../pro/plus-core
|
version: link:../../pro/plus-core
|
||||||
dayjs:
|
dayjs:
|
||||||
specifier: ^1.11.7
|
specifier: ^1.11.7
|
||||||
@@ -457,7 +457,7 @@ importers:
|
|||||||
packages/libs/lib-k8s:
|
packages/libs/lib-k8s:
|
||||||
dependencies:
|
dependencies:
|
||||||
'@certd/basic':
|
'@certd/basic':
|
||||||
specifier: ^1.39.12
|
specifier: ^1.39.16
|
||||||
version: link:../../core/basic
|
version: link:../../core/basic
|
||||||
'@kubernetes/client-node':
|
'@kubernetes/client-node':
|
||||||
specifier: 0.21.0
|
specifier: 0.21.0
|
||||||
@@ -503,19 +503,19 @@ importers:
|
|||||||
packages/libs/lib-server:
|
packages/libs/lib-server:
|
||||||
dependencies:
|
dependencies:
|
||||||
'@certd/acme-client':
|
'@certd/acme-client':
|
||||||
specifier: ^1.39.12
|
specifier: ^1.39.16
|
||||||
version: link:../../core/acme-client
|
version: link:../../core/acme-client
|
||||||
'@certd/basic':
|
'@certd/basic':
|
||||||
specifier: ^1.39.12
|
specifier: ^1.39.16
|
||||||
version: link:../../core/basic
|
version: link:../../core/basic
|
||||||
'@certd/pipeline':
|
'@certd/pipeline':
|
||||||
specifier: ^1.39.12
|
specifier: ^1.39.16
|
||||||
version: link:../../core/pipeline
|
version: link:../../core/pipeline
|
||||||
'@certd/plugin-lib':
|
'@certd/plugin-lib':
|
||||||
specifier: ^1.39.12
|
specifier: ^1.39.16
|
||||||
version: link:../../plugins/plugin-lib
|
version: link:../../plugins/plugin-lib
|
||||||
'@certd/plus-core':
|
'@certd/plus-core':
|
||||||
specifier: ^1.39.12
|
specifier: ^1.39.16
|
||||||
version: link:../../pro/plus-core
|
version: link:../../pro/plus-core
|
||||||
'@midwayjs/cache':
|
'@midwayjs/cache':
|
||||||
specifier: 3.14.0
|
specifier: 3.14.0
|
||||||
@@ -679,16 +679,16 @@ importers:
|
|||||||
packages/plugins/plugin-cert:
|
packages/plugins/plugin-cert:
|
||||||
dependencies:
|
dependencies:
|
||||||
'@certd/acme-client':
|
'@certd/acme-client':
|
||||||
specifier: ^1.39.12
|
specifier: ^1.39.16
|
||||||
version: link:../../core/acme-client
|
version: link:../../core/acme-client
|
||||||
'@certd/basic':
|
'@certd/basic':
|
||||||
specifier: ^1.39.12
|
specifier: ^1.39.16
|
||||||
version: link:../../core/basic
|
version: link:../../core/basic
|
||||||
'@certd/pipeline':
|
'@certd/pipeline':
|
||||||
specifier: ^1.39.12
|
specifier: ^1.39.16
|
||||||
version: link:../../core/pipeline
|
version: link:../../core/pipeline
|
||||||
'@certd/plugin-lib':
|
'@certd/plugin-lib':
|
||||||
specifier: ^1.39.12
|
specifier: ^1.39.16
|
||||||
version: link:../plugin-lib
|
version: link:../plugin-lib
|
||||||
psl:
|
psl:
|
||||||
specifier: ^1.9.0
|
specifier: ^1.9.0
|
||||||
@@ -758,16 +758,16 @@ importers:
|
|||||||
specifier: ^3.964.0
|
specifier: ^3.964.0
|
||||||
version: 3.964.0(aws-crt@1.26.2)
|
version: 3.964.0(aws-crt@1.26.2)
|
||||||
'@certd/acme-client':
|
'@certd/acme-client':
|
||||||
specifier: ^1.39.12
|
specifier: ^1.39.16
|
||||||
version: link:../../core/acme-client
|
version: link:../../core/acme-client
|
||||||
'@certd/basic':
|
'@certd/basic':
|
||||||
specifier: ^1.39.12
|
specifier: ^1.39.16
|
||||||
version: link:../../core/basic
|
version: link:../../core/basic
|
||||||
'@certd/pipeline':
|
'@certd/pipeline':
|
||||||
specifier: ^1.39.12
|
specifier: ^1.39.16
|
||||||
version: link:../../core/pipeline
|
version: link:../../core/pipeline
|
||||||
'@certd/plus-core':
|
'@certd/plus-core':
|
||||||
specifier: ^1.39.12
|
specifier: ^1.39.16
|
||||||
version: link:../../pro/plus-core
|
version: link:../../pro/plus-core
|
||||||
'@kubernetes/client-node':
|
'@kubernetes/client-node':
|
||||||
specifier: 0.21.0
|
specifier: 0.21.0
|
||||||
@@ -867,16 +867,16 @@ importers:
|
|||||||
packages/pro/commercial-core:
|
packages/pro/commercial-core:
|
||||||
dependencies:
|
dependencies:
|
||||||
'@certd/basic':
|
'@certd/basic':
|
||||||
specifier: ^1.39.12
|
specifier: ^1.39.16
|
||||||
version: link:../../core/basic
|
version: link:../../core/basic
|
||||||
'@certd/lib-server':
|
'@certd/lib-server':
|
||||||
specifier: ^1.39.12
|
specifier: ^1.39.16
|
||||||
version: link:../../libs/lib-server
|
version: link:../../libs/lib-server
|
||||||
'@certd/pipeline':
|
'@certd/pipeline':
|
||||||
specifier: ^1.39.12
|
specifier: ^1.39.16
|
||||||
version: link:../../core/pipeline
|
version: link:../../core/pipeline
|
||||||
'@certd/plus-core':
|
'@certd/plus-core':
|
||||||
specifier: ^1.39.12
|
specifier: ^1.39.16
|
||||||
version: link:../plus-core
|
version: link:../plus-core
|
||||||
'@midwayjs/core':
|
'@midwayjs/core':
|
||||||
specifier: 3.20.11
|
specifier: 3.20.11
|
||||||
@@ -967,16 +967,16 @@ importers:
|
|||||||
packages/pro/plugin-plus:
|
packages/pro/plugin-plus:
|
||||||
dependencies:
|
dependencies:
|
||||||
'@certd/basic':
|
'@certd/basic':
|
||||||
specifier: ^1.39.12
|
specifier: ^1.39.16
|
||||||
version: link:../../core/basic
|
version: link:../../core/basic
|
||||||
'@certd/pipeline':
|
'@certd/pipeline':
|
||||||
specifier: ^1.39.12
|
specifier: ^1.39.16
|
||||||
version: link:../../core/pipeline
|
version: link:../../core/pipeline
|
||||||
'@certd/plugin-lib':
|
'@certd/plugin-lib':
|
||||||
specifier: ^1.39.12
|
specifier: ^1.39.16
|
||||||
version: link:../../plugins/plugin-lib
|
version: link:../../plugins/plugin-lib
|
||||||
'@certd/plus-core':
|
'@certd/plus-core':
|
||||||
specifier: ^1.39.12
|
specifier: ^1.39.16
|
||||||
version: link:../plus-core
|
version: link:../plus-core
|
||||||
crypto-js:
|
crypto-js:
|
||||||
specifier: ^4.2.0
|
specifier: ^4.2.0
|
||||||
@@ -1061,7 +1061,7 @@ importers:
|
|||||||
packages/pro/plus-core:
|
packages/pro/plus-core:
|
||||||
dependencies:
|
dependencies:
|
||||||
'@certd/basic':
|
'@certd/basic':
|
||||||
specifier: ^1.39.12
|
specifier: ^1.39.16
|
||||||
version: link:../../core/basic
|
version: link:../../core/basic
|
||||||
dayjs:
|
dayjs:
|
||||||
specifier: ^1.11.7
|
specifier: ^1.11.7
|
||||||
@@ -1152,20 +1152,20 @@ importers:
|
|||||||
specifier: ^4.1.0
|
specifier: ^4.1.0
|
||||||
version: 4.1.0
|
version: 4.1.0
|
||||||
'@fast-crud/editor-code':
|
'@fast-crud/editor-code':
|
||||||
specifier: ^1.27.8
|
specifier: ^1.28.1
|
||||||
version: 1.27.8
|
version: 1.28.1
|
||||||
'@fast-crud/fast-crud':
|
'@fast-crud/fast-crud':
|
||||||
specifier: ^1.27.8
|
specifier: ^1.28.1
|
||||||
version: 1.27.8(vue@3.5.14(typescript@5.9.3))
|
version: 1.28.1(vue@3.5.14(typescript@5.9.3))
|
||||||
'@fast-crud/fast-extends':
|
'@fast-crud/fast-extends':
|
||||||
specifier: ^1.27.8
|
specifier: ^1.28.1
|
||||||
version: 1.27.8(aws-crt@1.26.2)(vue@3.5.14(typescript@5.9.3))
|
version: 1.28.1(aws-crt@1.26.2)(vue@3.5.14(typescript@5.9.3))
|
||||||
'@fast-crud/ui-antdv4':
|
'@fast-crud/ui-antdv4':
|
||||||
specifier: ^1.27.8
|
specifier: ^1.28.1
|
||||||
version: 1.27.8
|
version: 1.28.1
|
||||||
'@fast-crud/ui-interface':
|
'@fast-crud/ui-interface':
|
||||||
specifier: ^1.27.8
|
specifier: ^1.28.1
|
||||||
version: 1.27.8
|
version: 1.28.1
|
||||||
'@iconify/tailwind':
|
'@iconify/tailwind':
|
||||||
specifier: ^1.2.0
|
specifier: ^1.2.0
|
||||||
version: 1.2.0
|
version: 1.2.0
|
||||||
@@ -1363,10 +1363,10 @@ importers:
|
|||||||
version: 0.1.3(zod@3.24.4)
|
version: 0.1.3(zod@3.24.4)
|
||||||
devDependencies:
|
devDependencies:
|
||||||
'@certd/lib-iframe':
|
'@certd/lib-iframe':
|
||||||
specifier: ^1.39.12
|
specifier: ^1.39.16
|
||||||
version: link:../../libs/lib-iframe
|
version: link:../../libs/lib-iframe
|
||||||
'@certd/pipeline':
|
'@certd/pipeline':
|
||||||
specifier: ^1.39.12
|
specifier: ^1.39.16
|
||||||
version: link:../../core/pipeline
|
version: link:../../core/pipeline
|
||||||
'@rollup/plugin-commonjs':
|
'@rollup/plugin-commonjs':
|
||||||
specifier: ^25.0.7
|
specifier: ^25.0.7
|
||||||
@@ -1573,46 +1573,46 @@ importers:
|
|||||||
specifier: ^4.13.1
|
specifier: ^4.13.1
|
||||||
version: 4.13.1
|
version: 4.13.1
|
||||||
'@certd/acme-client':
|
'@certd/acme-client':
|
||||||
specifier: ^1.39.12
|
specifier: ^1.39.16
|
||||||
version: link:../../core/acme-client
|
version: link:../../core/acme-client
|
||||||
'@certd/basic':
|
'@certd/basic':
|
||||||
specifier: ^1.39.12
|
specifier: ^1.39.16
|
||||||
version: link:../../core/basic
|
version: link:../../core/basic
|
||||||
'@certd/commercial-core':
|
'@certd/commercial-core':
|
||||||
specifier: ^1.39.12
|
specifier: ^1.39.16
|
||||||
version: link:../../pro/commercial-core
|
version: link:../../pro/commercial-core
|
||||||
'@certd/cv4pve-api-javascript':
|
'@certd/cv4pve-api-javascript':
|
||||||
specifier: ^8.4.2
|
specifier: ^8.4.2
|
||||||
version: 8.4.2
|
version: 8.4.2
|
||||||
'@certd/jdcloud':
|
'@certd/jdcloud':
|
||||||
specifier: ^1.39.12
|
specifier: ^1.39.16
|
||||||
version: link:../../libs/lib-jdcloud
|
version: link:../../libs/lib-jdcloud
|
||||||
'@certd/lib-huawei':
|
'@certd/lib-huawei':
|
||||||
specifier: ^1.39.12
|
specifier: ^1.39.16
|
||||||
version: link:../../libs/lib-huawei
|
version: link:../../libs/lib-huawei
|
||||||
'@certd/lib-k8s':
|
'@certd/lib-k8s':
|
||||||
specifier: ^1.39.12
|
specifier: ^1.39.16
|
||||||
version: link:../../libs/lib-k8s
|
version: link:../../libs/lib-k8s
|
||||||
'@certd/lib-server':
|
'@certd/lib-server':
|
||||||
specifier: ^1.39.12
|
specifier: ^1.39.16
|
||||||
version: link:../../libs/lib-server
|
version: link:../../libs/lib-server
|
||||||
'@certd/midway-flyway-js':
|
'@certd/midway-flyway-js':
|
||||||
specifier: ^1.39.12
|
specifier: ^1.39.16
|
||||||
version: link:../../libs/midway-flyway-js
|
version: link:../../libs/midway-flyway-js
|
||||||
'@certd/pipeline':
|
'@certd/pipeline':
|
||||||
specifier: ^1.39.12
|
specifier: ^1.39.16
|
||||||
version: link:../../core/pipeline
|
version: link:../../core/pipeline
|
||||||
'@certd/plugin-cert':
|
'@certd/plugin-cert':
|
||||||
specifier: ^1.39.12
|
specifier: ^1.39.16
|
||||||
version: link:../../plugins/plugin-cert
|
version: link:../../plugins/plugin-cert
|
||||||
'@certd/plugin-lib':
|
'@certd/plugin-lib':
|
||||||
specifier: ^1.39.12
|
specifier: ^1.39.16
|
||||||
version: link:../../plugins/plugin-lib
|
version: link:../../plugins/plugin-lib
|
||||||
'@certd/plugin-plus':
|
'@certd/plugin-plus':
|
||||||
specifier: ^1.39.12
|
specifier: ^1.39.16
|
||||||
version: link:../../pro/plugin-plus
|
version: link:../../pro/plugin-plus
|
||||||
'@certd/plus-core':
|
'@certd/plus-core':
|
||||||
specifier: ^1.39.12
|
specifier: ^1.39.16
|
||||||
version: link:../../pro/plus-core
|
version: link:../../pro/plus-core
|
||||||
'@google-cloud/dns':
|
'@google-cloud/dns':
|
||||||
specifier: ^5.3.1
|
specifier: ^5.3.1
|
||||||
@@ -3675,20 +3675,20 @@ packages:
|
|||||||
resolution: {integrity: sha512-Ys+3g2TaW7gADOJzPt83SJtCDhMjndcDMFVQ/Tj9iA1BfJzFKD9mAUXT3OenpuPHbI6P/myECxRJrofUsDx/5g==}
|
resolution: {integrity: sha512-Ys+3g2TaW7gADOJzPt83SJtCDhMjndcDMFVQ/Tj9iA1BfJzFKD9mAUXT3OenpuPHbI6P/myECxRJrofUsDx/5g==}
|
||||||
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
|
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
|
||||||
|
|
||||||
'@fast-crud/editor-code@1.27.8':
|
'@fast-crud/editor-code@1.28.1':
|
||||||
resolution: {integrity: sha512-NOIFFMNR+ZZ3dikp6U/V2zDcWcaT+bIq6hV9953keSjLMtbbOcn9PDw/oByac7TXy8l0jCI+4Ee89OepRGqa9Q==}
|
resolution: {integrity: sha512-5BCN916KQ3zn1TbAQJiTdhonBN4i8NWCCYKateuBWnaOM7B7zgULC3+Jncn1JlAnCaH7L+7iYwf8r/XNC0DT7w==}
|
||||||
|
|
||||||
'@fast-crud/fast-crud@1.27.8':
|
'@fast-crud/fast-crud@1.28.1':
|
||||||
resolution: {integrity: sha512-yv1tg9qxNwFVnvmo882l2fdL0q6tghg5QQ7S5dsjm7wRf4IxFWqLHOnBor7GUbo+fGBxJK5H8DQsK/HB2De/nA==}
|
resolution: {integrity: sha512-E9XwwQsNkP77kqLTJyqy/k1i7KD7cnw6FkJt3fTMl1OsBjp09TY3a4OXEfcsJsk+q/l8/9HzQRDBDhDIcWkROA==}
|
||||||
|
|
||||||
'@fast-crud/fast-extends@1.27.8':
|
'@fast-crud/fast-extends@1.28.1':
|
||||||
resolution: {integrity: sha512-C9Lc4n/mZp8M5xT+8031rnTh518jfpHsww9PXXnV7XJM/Nleo04rP2rDH5e+J8MVSsZRC5j93TfIjhcLDc/JYg==}
|
resolution: {integrity: sha512-HK2bktH+PYKJlPeTgO9MTIofdpe9g3s2eaBodGo6A7LgXp9U9aTHbfJJsRxzh8DTM8igi71gHg45bTL3DMLYuQ==}
|
||||||
|
|
||||||
'@fast-crud/ui-antdv4@1.27.8':
|
'@fast-crud/ui-antdv4@1.28.1':
|
||||||
resolution: {integrity: sha512-nig8zAq6DbJNuQ6PkrDJjpNfH2B2R0LDE9foW1krtk1bM5nzn5bIAvJ5MHcVxFYiOwqgzke/m6h7RGquPLbLjg==}
|
resolution: {integrity: sha512-qJinotmWmbd4WdZlkaVv/eRqzbTgIWWQyQgZMK7LlOmRe/83ZpXcq45hap0+pH5UD7ZJrKA5RIIgGFp5+ihvvg==}
|
||||||
|
|
||||||
'@fast-crud/ui-interface@1.27.8':
|
'@fast-crud/ui-interface@1.28.1':
|
||||||
resolution: {integrity: sha512-qsTHI7QBmgxlO8ZOoTBKN7zZOUhaYyCtPIUQ5/VJiaBhsWDsQMQTn/7rhPXU5WNpjtfdR2cFW7MgTemVmiDvDQ==}
|
resolution: {integrity: sha512-HUUO/SObmkAYnQdSETa4Z+M1YMJvzgQWBmy453iPKApTCxf2hEv4Alj/KuajZuP2sxFNzvsMdaUyAO6vWRBaDw==}
|
||||||
|
|
||||||
'@fidm/asn1@1.0.4':
|
'@fidm/asn1@1.0.4':
|
||||||
resolution: {integrity: sha512-esd1jyNvRb2HVaQGq2Gg8Z0kbQPXzV9Tq5Z14KNIov6KfFD6PTaRIO8UpcsYiTNzOqJpmyzWgVTrUwFV3UF4TQ==}
|
resolution: {integrity: sha512-esd1jyNvRb2HVaQGq2Gg8Z0kbQPXzV9Tq5Z14KNIov6KfFD6PTaRIO8UpcsYiTNzOqJpmyzWgVTrUwFV3UF4TQ==}
|
||||||
@@ -4471,67 +4471,56 @@ packages:
|
|||||||
resolution: {integrity: sha512-u72Mzc6jyJwKjJbZZcIYmd9bumJu7KNmHYdue43vT1rXPm2rITwmPWF0mmPzLm9/vJWxIRbao/jrQmxTO0Sm9w==}
|
resolution: {integrity: sha512-u72Mzc6jyJwKjJbZZcIYmd9bumJu7KNmHYdue43vT1rXPm2rITwmPWF0mmPzLm9/vJWxIRbao/jrQmxTO0Sm9w==}
|
||||||
cpu: [arm]
|
cpu: [arm]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
libc: [glibc]
|
|
||||||
|
|
||||||
'@rollup/rollup-linux-arm-musleabihf@4.50.0':
|
'@rollup/rollup-linux-arm-musleabihf@4.50.0':
|
||||||
resolution: {integrity: sha512-S4UefYdV0tnynDJV1mdkNawp0E5Qm2MtSs330IyHgaccOFrwqsvgigUD29uT+B/70PDY1eQ3t40+xf6wIvXJyg==}
|
resolution: {integrity: sha512-S4UefYdV0tnynDJV1mdkNawp0E5Qm2MtSs330IyHgaccOFrwqsvgigUD29uT+B/70PDY1eQ3t40+xf6wIvXJyg==}
|
||||||
cpu: [arm]
|
cpu: [arm]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
libc: [musl]
|
|
||||||
|
|
||||||
'@rollup/rollup-linux-arm64-gnu@4.50.0':
|
'@rollup/rollup-linux-arm64-gnu@4.50.0':
|
||||||
resolution: {integrity: sha512-1EhkSvUQXJsIhk4msxP5nNAUWoB4MFDHhtc4gAYvnqoHlaL9V3F37pNHabndawsfy/Tp7BPiy/aSa6XBYbaD1g==}
|
resolution: {integrity: sha512-1EhkSvUQXJsIhk4msxP5nNAUWoB4MFDHhtc4gAYvnqoHlaL9V3F37pNHabndawsfy/Tp7BPiy/aSa6XBYbaD1g==}
|
||||||
cpu: [arm64]
|
cpu: [arm64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
libc: [glibc]
|
|
||||||
|
|
||||||
'@rollup/rollup-linux-arm64-musl@4.50.0':
|
'@rollup/rollup-linux-arm64-musl@4.50.0':
|
||||||
resolution: {integrity: sha512-EtBDIZuDtVg75xIPIK1l5vCXNNCIRM0OBPUG+tbApDuJAy9mKago6QxX+tfMzbCI6tXEhMuZuN1+CU8iDW+0UQ==}
|
resolution: {integrity: sha512-EtBDIZuDtVg75xIPIK1l5vCXNNCIRM0OBPUG+tbApDuJAy9mKago6QxX+tfMzbCI6tXEhMuZuN1+CU8iDW+0UQ==}
|
||||||
cpu: [arm64]
|
cpu: [arm64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
libc: [musl]
|
|
||||||
|
|
||||||
'@rollup/rollup-linux-loongarch64-gnu@4.50.0':
|
'@rollup/rollup-linux-loongarch64-gnu@4.50.0':
|
||||||
resolution: {integrity: sha512-BGYSwJdMP0hT5CCmljuSNx7+k+0upweM2M4YGfFBjnFSZMHOLYR0gEEj/dxyYJ6Zc6AiSeaBY8dWOa11GF/ppQ==}
|
resolution: {integrity: sha512-BGYSwJdMP0hT5CCmljuSNx7+k+0upweM2M4YGfFBjnFSZMHOLYR0gEEj/dxyYJ6Zc6AiSeaBY8dWOa11GF/ppQ==}
|
||||||
cpu: [loong64]
|
cpu: [loong64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
libc: [glibc]
|
|
||||||
|
|
||||||
'@rollup/rollup-linux-ppc64-gnu@4.50.0':
|
'@rollup/rollup-linux-ppc64-gnu@4.50.0':
|
||||||
resolution: {integrity: sha512-I1gSMzkVe1KzAxKAroCJL30hA4DqSi+wGc5gviD0y3IL/VkvcnAqwBf4RHXHyvH66YVHxpKO8ojrgc4SrWAnLg==}
|
resolution: {integrity: sha512-I1gSMzkVe1KzAxKAroCJL30hA4DqSi+wGc5gviD0y3IL/VkvcnAqwBf4RHXHyvH66YVHxpKO8ojrgc4SrWAnLg==}
|
||||||
cpu: [ppc64]
|
cpu: [ppc64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
libc: [glibc]
|
|
||||||
|
|
||||||
'@rollup/rollup-linux-riscv64-gnu@4.50.0':
|
'@rollup/rollup-linux-riscv64-gnu@4.50.0':
|
||||||
resolution: {integrity: sha512-bSbWlY3jZo7molh4tc5dKfeSxkqnf48UsLqYbUhnkdnfgZjgufLS/NTA8PcP/dnvct5CCdNkABJ56CbclMRYCA==}
|
resolution: {integrity: sha512-bSbWlY3jZo7molh4tc5dKfeSxkqnf48UsLqYbUhnkdnfgZjgufLS/NTA8PcP/dnvct5CCdNkABJ56CbclMRYCA==}
|
||||||
cpu: [riscv64]
|
cpu: [riscv64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
libc: [glibc]
|
|
||||||
|
|
||||||
'@rollup/rollup-linux-riscv64-musl@4.50.0':
|
'@rollup/rollup-linux-riscv64-musl@4.50.0':
|
||||||
resolution: {integrity: sha512-LSXSGumSURzEQLT2e4sFqFOv3LWZsEF8FK7AAv9zHZNDdMnUPYH3t8ZlaeYYZyTXnsob3htwTKeWtBIkPV27iQ==}
|
resolution: {integrity: sha512-LSXSGumSURzEQLT2e4sFqFOv3LWZsEF8FK7AAv9zHZNDdMnUPYH3t8ZlaeYYZyTXnsob3htwTKeWtBIkPV27iQ==}
|
||||||
cpu: [riscv64]
|
cpu: [riscv64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
libc: [musl]
|
|
||||||
|
|
||||||
'@rollup/rollup-linux-s390x-gnu@4.50.0':
|
'@rollup/rollup-linux-s390x-gnu@4.50.0':
|
||||||
resolution: {integrity: sha512-CxRKyakfDrsLXiCyucVfVWVoaPA4oFSpPpDwlMcDFQvrv3XY6KEzMtMZrA+e/goC8xxp2WSOxHQubP8fPmmjOQ==}
|
resolution: {integrity: sha512-CxRKyakfDrsLXiCyucVfVWVoaPA4oFSpPpDwlMcDFQvrv3XY6KEzMtMZrA+e/goC8xxp2WSOxHQubP8fPmmjOQ==}
|
||||||
cpu: [s390x]
|
cpu: [s390x]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
libc: [glibc]
|
|
||||||
|
|
||||||
'@rollup/rollup-linux-x64-gnu@4.50.0':
|
'@rollup/rollup-linux-x64-gnu@4.50.0':
|
||||||
resolution: {integrity: sha512-8PrJJA7/VU8ToHVEPu14FzuSAqVKyo5gg/J8xUerMbyNkWkO9j2ExBho/68RnJsMGNJq4zH114iAttgm7BZVkA==}
|
resolution: {integrity: sha512-8PrJJA7/VU8ToHVEPu14FzuSAqVKyo5gg/J8xUerMbyNkWkO9j2ExBho/68RnJsMGNJq4zH114iAttgm7BZVkA==}
|
||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
libc: [glibc]
|
|
||||||
|
|
||||||
'@rollup/rollup-linux-x64-musl@4.50.0':
|
'@rollup/rollup-linux-x64-musl@4.50.0':
|
||||||
resolution: {integrity: sha512-SkE6YQp+CzpyOrbw7Oc4MgXFvTw2UIBElvAvLCo230pyxOLmYwRPwZ/L5lBe/VW/qT1ZgND9wJfOsdy0XptRvw==}
|
resolution: {integrity: sha512-SkE6YQp+CzpyOrbw7Oc4MgXFvTw2UIBElvAvLCo230pyxOLmYwRPwZ/L5lBe/VW/qT1ZgND9wJfOsdy0XptRvw==}
|
||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
libc: [musl]
|
|
||||||
|
|
||||||
'@rollup/rollup-openharmony-arm64@4.50.0':
|
'@rollup/rollup-openharmony-arm64@4.50.0':
|
||||||
resolution: {integrity: sha512-PZkNLPfvXeIOgJWA804zjSFH7fARBBCpCXxgkGDRjjAhRLOR8o0IGS01ykh5GYfod4c2yiiREuDM8iZ+pVsT+Q==}
|
resolution: {integrity: sha512-PZkNLPfvXeIOgJWA804zjSFH7fARBBCpCXxgkGDRjjAhRLOR8o0IGS01ykh5GYfod4c2yiiREuDM8iZ+pVsT+Q==}
|
||||||
@@ -14015,14 +14004,14 @@ snapshots:
|
|||||||
'@aws-sdk/core@3.810.0':
|
'@aws-sdk/core@3.810.0':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@aws-sdk/types': 3.804.0
|
'@aws-sdk/types': 3.804.0
|
||||||
'@smithy/core': 3.20.0
|
'@smithy/core': 3.23.0
|
||||||
'@smithy/node-config-provider': 4.3.7
|
'@smithy/node-config-provider': 4.3.8
|
||||||
'@smithy/property-provider': 4.2.7
|
'@smithy/property-provider': 4.2.8
|
||||||
'@smithy/protocol-http': 5.3.7
|
'@smithy/protocol-http': 5.3.8
|
||||||
'@smithy/signature-v4': 5.3.7
|
'@smithy/signature-v4': 5.3.8
|
||||||
'@smithy/smithy-client': 4.10.2
|
'@smithy/smithy-client': 4.11.3
|
||||||
'@smithy/types': 4.11.0
|
'@smithy/types': 4.12.0
|
||||||
'@smithy/util-middleware': 4.2.7
|
'@smithy/util-middleware': 4.2.8
|
||||||
fast-xml-parser: 4.4.1
|
fast-xml-parser: 4.4.1
|
||||||
tslib: 2.8.1
|
tslib: 2.8.1
|
||||||
|
|
||||||
@@ -14375,15 +14364,15 @@ snapshots:
|
|||||||
'@aws-sdk/core': 3.810.0
|
'@aws-sdk/core': 3.810.0
|
||||||
'@aws-sdk/types': 3.804.0
|
'@aws-sdk/types': 3.804.0
|
||||||
'@aws-sdk/util-arn-parser': 3.804.0
|
'@aws-sdk/util-arn-parser': 3.804.0
|
||||||
'@smithy/core': 3.20.0
|
'@smithy/core': 3.23.0
|
||||||
'@smithy/node-config-provider': 4.3.7
|
'@smithy/node-config-provider': 4.3.8
|
||||||
'@smithy/protocol-http': 5.3.7
|
'@smithy/protocol-http': 5.3.8
|
||||||
'@smithy/signature-v4': 5.3.7
|
'@smithy/signature-v4': 5.3.8
|
||||||
'@smithy/smithy-client': 4.10.2
|
'@smithy/smithy-client': 4.11.3
|
||||||
'@smithy/types': 4.11.0
|
'@smithy/types': 4.12.0
|
||||||
'@smithy/util-config-provider': 4.2.0
|
'@smithy/util-config-provider': 4.2.0
|
||||||
'@smithy/util-middleware': 4.2.7
|
'@smithy/util-middleware': 4.2.8
|
||||||
'@smithy/util-stream': 4.5.8
|
'@smithy/util-stream': 4.5.12
|
||||||
'@smithy/util-utf8': 4.2.0
|
'@smithy/util-utf8': 4.2.0
|
||||||
tslib: 2.8.1
|
tslib: 2.8.1
|
||||||
|
|
||||||
@@ -14415,9 +14404,9 @@ snapshots:
|
|||||||
'@aws-sdk/core': 3.810.0
|
'@aws-sdk/core': 3.810.0
|
||||||
'@aws-sdk/types': 3.804.0
|
'@aws-sdk/types': 3.804.0
|
||||||
'@aws-sdk/util-endpoints': 3.808.0
|
'@aws-sdk/util-endpoints': 3.808.0
|
||||||
'@smithy/core': 3.20.0
|
'@smithy/core': 3.23.0
|
||||||
'@smithy/protocol-http': 5.3.7
|
'@smithy/protocol-http': 5.3.8
|
||||||
'@smithy/types': 4.11.0
|
'@smithy/types': 4.12.0
|
||||||
tslib: 2.8.1
|
tslib: 2.8.1
|
||||||
|
|
||||||
'@aws-sdk/middleware-user-agent@3.964.0':
|
'@aws-sdk/middleware-user-agent@3.964.0':
|
||||||
@@ -14558,10 +14547,10 @@ snapshots:
|
|||||||
'@aws-sdk/crt-loader': 3.810.0
|
'@aws-sdk/crt-loader': 3.810.0
|
||||||
'@aws-sdk/signature-v4-multi-region': 3.810.0
|
'@aws-sdk/signature-v4-multi-region': 3.810.0
|
||||||
'@aws-sdk/types': 3.804.0
|
'@aws-sdk/types': 3.804.0
|
||||||
'@smithy/querystring-parser': 4.2.7
|
'@smithy/querystring-parser': 4.2.8
|
||||||
'@smithy/signature-v4': 5.3.7
|
'@smithy/signature-v4': 5.3.8
|
||||||
'@smithy/types': 4.11.0
|
'@smithy/types': 4.12.0
|
||||||
'@smithy/util-middleware': 4.2.7
|
'@smithy/util-middleware': 4.2.8
|
||||||
tslib: 2.8.1
|
tslib: 2.8.1
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- bufferutil
|
- bufferutil
|
||||||
@@ -14573,9 +14562,9 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
'@aws-sdk/middleware-sdk-s3': 3.810.0
|
'@aws-sdk/middleware-sdk-s3': 3.810.0
|
||||||
'@aws-sdk/types': 3.804.0
|
'@aws-sdk/types': 3.804.0
|
||||||
'@smithy/protocol-http': 5.3.7
|
'@smithy/protocol-http': 5.3.8
|
||||||
'@smithy/signature-v4': 5.3.7
|
'@smithy/signature-v4': 5.3.8
|
||||||
'@smithy/types': 4.11.0
|
'@smithy/types': 4.12.0
|
||||||
tslib: 2.8.1
|
tslib: 2.8.1
|
||||||
|
|
||||||
'@aws-sdk/signature-v4-multi-region@3.964.0':
|
'@aws-sdk/signature-v4-multi-region@3.964.0':
|
||||||
@@ -14613,7 +14602,7 @@ snapshots:
|
|||||||
|
|
||||||
'@aws-sdk/types@3.804.0':
|
'@aws-sdk/types@3.804.0':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@smithy/types': 4.11.0
|
'@smithy/types': 4.12.0
|
||||||
tslib: 2.8.1
|
tslib: 2.8.1
|
||||||
|
|
||||||
'@aws-sdk/types@3.957.0':
|
'@aws-sdk/types@3.957.0':
|
||||||
@@ -14637,8 +14626,8 @@ snapshots:
|
|||||||
'@aws-sdk/util-endpoints@3.808.0':
|
'@aws-sdk/util-endpoints@3.808.0':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@aws-sdk/types': 3.804.0
|
'@aws-sdk/types': 3.804.0
|
||||||
'@smithy/types': 4.11.0
|
'@smithy/types': 4.12.0
|
||||||
'@smithy/util-endpoints': 3.2.7
|
'@smithy/util-endpoints': 3.2.8
|
||||||
tslib: 2.8.1
|
tslib: 2.8.1
|
||||||
|
|
||||||
'@aws-sdk/util-endpoints@3.957.0':
|
'@aws-sdk/util-endpoints@3.957.0':
|
||||||
@@ -14686,8 +14675,8 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
'@aws-sdk/middleware-user-agent': 3.810.0
|
'@aws-sdk/middleware-user-agent': 3.810.0
|
||||||
'@aws-sdk/types': 3.804.0
|
'@aws-sdk/types': 3.804.0
|
||||||
'@smithy/node-config-provider': 4.3.7
|
'@smithy/node-config-provider': 4.3.8
|
||||||
'@smithy/types': 4.11.0
|
'@smithy/types': 4.12.0
|
||||||
tslib: 2.8.1
|
tslib: 2.8.1
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
aws-crt: 1.26.2
|
aws-crt: 1.26.2
|
||||||
@@ -16092,14 +16081,14 @@ snapshots:
|
|||||||
|
|
||||||
'@eslint/js@8.57.0': {}
|
'@eslint/js@8.57.0': {}
|
||||||
|
|
||||||
'@fast-crud/editor-code@1.27.8':
|
'@fast-crud/editor-code@1.28.1':
|
||||||
dependencies:
|
dependencies:
|
||||||
js-yaml: 4.1.0
|
js-yaml: 4.1.0
|
||||||
lodash-es: 4.17.21
|
lodash-es: 4.17.21
|
||||||
monaco-editor: 0.52.2
|
monaco-editor: 0.52.2
|
||||||
monaco-yaml: 5.4.0(monaco-editor@0.52.2)
|
monaco-yaml: 5.4.0(monaco-editor@0.52.2)
|
||||||
|
|
||||||
'@fast-crud/fast-crud@1.27.8(vue@3.5.14(typescript@5.9.3))':
|
'@fast-crud/fast-crud@1.28.1(vue@3.5.14(typescript@5.9.3))':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@iconify/types': 2.0.0
|
'@iconify/types': 2.0.0
|
||||||
file-saver: 2.0.5
|
file-saver: 2.0.5
|
||||||
@@ -16109,7 +16098,7 @@ snapshots:
|
|||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- vue
|
- vue
|
||||||
|
|
||||||
'@fast-crud/fast-extends@1.27.8(aws-crt@1.26.2)(vue@3.5.14(typescript@5.9.3))':
|
'@fast-crud/fast-extends@1.28.1(aws-crt@1.26.2)(vue@3.5.14(typescript@5.9.3))':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@aws-sdk/client-s3': 3.964.0(aws-crt@1.26.2)
|
'@aws-sdk/client-s3': 3.964.0(aws-crt@1.26.2)
|
||||||
'@aws-sdk/s3-request-presigner': 3.964.0
|
'@aws-sdk/s3-request-presigner': 3.964.0
|
||||||
@@ -16139,9 +16128,9 @@ snapshots:
|
|||||||
- utf-8-validate
|
- utf-8-validate
|
||||||
- vue
|
- vue
|
||||||
|
|
||||||
'@fast-crud/ui-antdv4@1.27.8': {}
|
'@fast-crud/ui-antdv4@1.28.1': {}
|
||||||
|
|
||||||
'@fast-crud/ui-interface@1.27.8':
|
'@fast-crud/ui-interface@1.28.1':
|
||||||
dependencies:
|
dependencies:
|
||||||
lodash-es: 4.17.21
|
lodash-es: 4.17.21
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,89 @@
|
|||||||
|
# Certd OpenAPI SDK 示例
|
||||||
|
|
||||||
|
本目录提供 `/api/v1/cert/get` 接口的多语言 SDK 示例,演示如何封装 `CertdClient`,生成 `x-certd-token`,并按域名或证书 ID 获取证书。
|
||||||
|
|
||||||
|
## 目录结构
|
||||||
|
|
||||||
|
| 语言 | SDK 类/类型 | 调用示例 |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| Node.js | `sdk/nodejs/certd-client.js` | `sdk/nodejs/get-cert.js` |
|
||||||
|
| Python | `sdk/python/certd_client.py` | `sdk/python/get_cert.py` |
|
||||||
|
| Go | `sdk/go/certd_client.go` | `sdk/go/get_cert.go` |
|
||||||
|
| PHP | `sdk/php/CertdClient.php` | `sdk/php/get_cert.php` |
|
||||||
|
| Java | `sdk/java/CertdClient.java` | `sdk/java/GetCert.java` |
|
||||||
|
|
||||||
|
`CertdClient` 提供以下核心方法:
|
||||||
|
|
||||||
|
- `getSign(content)` / `GetSign(content)`:根据 `content + keySecret` 生成签名
|
||||||
|
- `getToken()` / `GetToken()`:生成 `x-certd-token`
|
||||||
|
- `request(path, body)` / `Request(path, body)`:携带 token 发起 OpenAPI 请求
|
||||||
|
- `getCert(params)` / `GetCert(params)`:调用 `/api/v1/cert/get` 的便捷方法
|
||||||
|
|
||||||
|
## 接口说明
|
||||||
|
|
||||||
|
- 请求地址:`POST /api/v1/cert/get`
|
||||||
|
- 认证方式:请求头传入 `x-certd-token`
|
||||||
|
- `certId` 和 `domains` 至少传一个;两个都传时,服务端优先使用 `certId`
|
||||||
|
- `autoApply=true` 时,如果没有可用证书,会尝试自动创建或触发流水线申请证书
|
||||||
|
- `format` 可选:`pem`、`jks`、`pfx`、`der`、`one`、`p7b`
|
||||||
|
|
||||||
|
## Token 生成规则
|
||||||
|
|
||||||
|
1. 在 OpenKey 页面生成 `keyId` 和 `keySecret`
|
||||||
|
2. 构造 JSON 字符串:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{"keyId":"你的 keyId","t":1710000000,"encrypt":false,"signType":"md5"}
|
||||||
|
```
|
||||||
|
|
||||||
|
3. 签名:`sign = md5(content + keySecret)`
|
||||||
|
4. Header:`x-certd-token = base64(content) + "." + base64(sign)`
|
||||||
|
|
||||||
|
注意:签名时必须使用和 base64 编码时完全相同的 `content` 字符串。
|
||||||
|
|
||||||
|
## 环境变量
|
||||||
|
|
||||||
|
所有示例都支持以下环境变量:
|
||||||
|
|
||||||
|
| 变量 | 说明 | 默认值 |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `CERTD_BASE_URL` | Certd 服务地址 | `http://127.0.0.1:7001` |
|
||||||
|
| `CERTD_KEY_ID` | OpenKey 的 keyId | 必填 |
|
||||||
|
| `CERTD_KEY_SECRET` | OpenKey 的 keySecret | 必填 |
|
||||||
|
| `CERTD_DOMAINS` | 域名列表,多个用英文逗号隔开 | 空 |
|
||||||
|
| `CERTD_CERT_ID` | 证书仓库证书 ID | 空 |
|
||||||
|
| `CERTD_AUTO_APPLY` | 不存在或过期时是否自动申请 | `false` |
|
||||||
|
| `CERTD_FORMAT` | 返回证书格式 | 空,表示返回所有格式 |
|
||||||
|
| `CERTD_ENCRYPT` | 是否要求接口加密返回结果 | `false` |
|
||||||
|
|
||||||
|
## 运行示例
|
||||||
|
|
||||||
|
PowerShell:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
$env:CERTD_BASE_URL = "http://127.0.0.1:7001"
|
||||||
|
$env:CERTD_KEY_ID = "your_key_id"
|
||||||
|
$env:CERTD_KEY_SECRET = "your_key_secret"
|
||||||
|
$env:CERTD_DOMAINS = "example.com,*.example.com"
|
||||||
|
$env:CERTD_AUTO_APPLY = "true"
|
||||||
|
$env:CERTD_FORMAT = "pem"
|
||||||
|
|
||||||
|
node sdk\nodejs\get-cert.js
|
||||||
|
python sdk\python\get_cert.py
|
||||||
|
go run sdk\go\certd_client.go sdk\go\get_cert.go
|
||||||
|
php sdk\php\get_cert.php
|
||||||
|
javac sdk\java\CertdClient.java sdk\java\GetCert.java && java -cp sdk\java GetCert
|
||||||
|
```
|
||||||
|
|
||||||
|
如果使用 `CERTD_CERT_ID` 获取证书,可以不传 `CERTD_DOMAINS`:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
$env:CERTD_CERT_ID = "1"
|
||||||
|
$env:CERTD_DOMAINS = ""
|
||||||
|
```
|
||||||
|
|
||||||
|
## 返回结果
|
||||||
|
|
||||||
|
`CERTD_ENCRYPT=false` 时,示例会直接打印接口返回的 JSON。
|
||||||
|
|
||||||
|
`CERTD_ENCRYPT=true` 时,接口返回内容会被服务端加密;这些示例只演示请求和 token 生成,不包含解密逻辑。
|
||||||
@@ -0,0 +1,107 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"crypto/md5"
|
||||||
|
"encoding/base64"
|
||||||
|
"encoding/hex"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type CertdClient struct {
|
||||||
|
KeyID string
|
||||||
|
KeySecret string
|
||||||
|
BaseURL string
|
||||||
|
Encrypt bool
|
||||||
|
SignType string
|
||||||
|
}
|
||||||
|
|
||||||
|
type tokenContent struct {
|
||||||
|
KeyID string `json:"keyId"`
|
||||||
|
T int64 `json:"t"`
|
||||||
|
Encrypt bool `json:"encrypt"`
|
||||||
|
SignType string `json:"signType"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewCertdClient(keyID, keySecret string) (*CertdClient, error) {
|
||||||
|
if keyID == "" {
|
||||||
|
return nil, fmt.Errorf("keyID is required")
|
||||||
|
}
|
||||||
|
if keySecret == "" {
|
||||||
|
return nil, fmt.Errorf("keySecret is required")
|
||||||
|
}
|
||||||
|
return &CertdClient{
|
||||||
|
KeyID: keyID,
|
||||||
|
KeySecret: keySecret,
|
||||||
|
BaseURL: "http://127.0.0.1:7001",
|
||||||
|
SignType: "md5",
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *CertdClient) GetSign(content string) (string, error) {
|
||||||
|
if c.SignType != "md5" {
|
||||||
|
return "", fmt.Errorf("unsupported signType: %s", c.SignType)
|
||||||
|
}
|
||||||
|
sum := md5.Sum([]byte(content + c.KeySecret))
|
||||||
|
return hex.EncodeToString(sum[:]), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *CertdClient) GetToken() (string, error) {
|
||||||
|
contentBytes, err := json.Marshal(tokenContent{
|
||||||
|
KeyID: c.KeyID,
|
||||||
|
T: time.Now().Unix(),
|
||||||
|
Encrypt: c.Encrypt,
|
||||||
|
SignType: c.SignType,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
content := string(contentBytes)
|
||||||
|
sign, err := c.GetSign(content)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return base64.StdEncoding.EncodeToString([]byte(content)) + "." + base64.StdEncoding.EncodeToString([]byte(sign)), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *CertdClient) Request(path string, body any) ([]byte, error) {
|
||||||
|
bodyBytes, err := json.Marshal(body)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
token, err := c.GetToken()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
httpReq, err := http.NewRequest(http.MethodPost, strings.TrimRight(c.BaseURL, "/")+path, bytes.NewReader(bodyBytes))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
httpReq.Header.Set("content-type", "application/json")
|
||||||
|
httpReq.Header.Set("x-certd-token", token)
|
||||||
|
|
||||||
|
resp, err := http.DefaultClient.Do(httpReq)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
respBody, err := io.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
|
||||||
|
return nil, fmt.Errorf("HTTP %d: %s", resp.StatusCode, string(respBody))
|
||||||
|
}
|
||||||
|
return respBody, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *CertdClient) GetCert(params any) ([]byte, error) {
|
||||||
|
return c.Request("/api/v1/cert/get", params)
|
||||||
|
}
|
||||||
@@ -0,0 +1,85 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
type certGetRequest struct {
|
||||||
|
Domains string `json:"domains,omitempty"`
|
||||||
|
CertID int64 `json:"certId,omitempty"`
|
||||||
|
AutoApply bool `json:"autoApply"`
|
||||||
|
Format string `json:"format,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func requireEnv(name string) (string, error) {
|
||||||
|
value := os.Getenv(name)
|
||||||
|
if value == "" {
|
||||||
|
return "", fmt.Errorf("missing environment variable: %s", name)
|
||||||
|
}
|
||||||
|
return value, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func boolEnv(name string, defaultValue bool) bool {
|
||||||
|
value := os.Getenv(name)
|
||||||
|
if value == "" {
|
||||||
|
return defaultValue
|
||||||
|
}
|
||||||
|
switch strings.ToLower(value) {
|
||||||
|
case "1", "true", "yes", "y":
|
||||||
|
return true
|
||||||
|
default:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
if err := run(); err != nil {
|
||||||
|
fmt.Fprintln(os.Stderr, err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func run() error {
|
||||||
|
keyID, err := requireEnv("CERTD_KEY_ID")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
keySecret, err := requireEnv("CERTD_KEY_SECRET")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
client, err := NewCertdClient(keyID, keySecret)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if baseURL := os.Getenv("CERTD_BASE_URL"); baseURL != "" {
|
||||||
|
client.BaseURL = baseURL
|
||||||
|
}
|
||||||
|
client.Encrypt = boolEnv("CERTD_ENCRYPT", false)
|
||||||
|
|
||||||
|
reqBody := certGetRequest{
|
||||||
|
Domains: os.Getenv("CERTD_DOMAINS"),
|
||||||
|
AutoApply: boolEnv("CERTD_AUTO_APPLY", false),
|
||||||
|
Format: os.Getenv("CERTD_FORMAT"),
|
||||||
|
}
|
||||||
|
if certID := os.Getenv("CERTD_CERT_ID"); certID != "" {
|
||||||
|
reqBody.CertID, err = strconv.ParseInt(certID, 10, 64)
|
||||||
|
if err != nil || reqBody.CertID <= 0 {
|
||||||
|
return fmt.Errorf("CERTD_CERT_ID must be a positive integer")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if reqBody.CertID == 0 && reqBody.Domains == "" {
|
||||||
|
return fmt.Errorf("set CERTD_CERT_ID or CERTD_DOMAINS")
|
||||||
|
}
|
||||||
|
|
||||||
|
respBody, err := client.GetCert(reqBody)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
fmt.Println(string(respBody))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,127 @@
|
|||||||
|
import java.io.IOException;
|
||||||
|
import java.net.URI;
|
||||||
|
import java.net.http.HttpClient;
|
||||||
|
import java.net.http.HttpRequest;
|
||||||
|
import java.net.http.HttpResponse;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.security.MessageDigest;
|
||||||
|
import java.time.Instant;
|
||||||
|
import java.util.Base64;
|
||||||
|
|
||||||
|
public class CertdClient {
|
||||||
|
private final String keyId;
|
||||||
|
private final String keySecret;
|
||||||
|
private final String baseUrl;
|
||||||
|
private final boolean encrypt;
|
||||||
|
private final String signType;
|
||||||
|
|
||||||
|
public CertdClient(String keyId, String keySecret) {
|
||||||
|
this(keyId, keySecret, "http://127.0.0.1:7001", false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public CertdClient(String keyId, String keySecret, String baseUrl, boolean encrypt) {
|
||||||
|
if (isBlank(keyId)) {
|
||||||
|
throw new IllegalArgumentException("keyId is required");
|
||||||
|
}
|
||||||
|
if (isBlank(keySecret)) {
|
||||||
|
throw new IllegalArgumentException("keySecret is required");
|
||||||
|
}
|
||||||
|
this.keyId = keyId;
|
||||||
|
this.keySecret = keySecret;
|
||||||
|
this.baseUrl = trimRightSlash(isBlank(baseUrl) ? "http://127.0.0.1:7001" : baseUrl);
|
||||||
|
this.encrypt = encrypt;
|
||||||
|
this.signType = "md5";
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getSign(String content) throws Exception {
|
||||||
|
if (!"md5".equals(signType)) {
|
||||||
|
throw new IllegalArgumentException("Unsupported signType: " + signType);
|
||||||
|
}
|
||||||
|
return md5Hex(content + keySecret);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getToken() throws Exception {
|
||||||
|
String content = "{\"keyId\":\"" + jsonEscape(keyId) + "\",\"t\":" + Instant.now().getEpochSecond()
|
||||||
|
+ ",\"encrypt\":" + encrypt + ",\"signType\":\"" + signType + "\"}";
|
||||||
|
String sign = getSign(content);
|
||||||
|
return base64(content) + "." + base64(sign);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String request(String path, String bodyJson) throws Exception {
|
||||||
|
HttpRequest request = HttpRequest.newBuilder()
|
||||||
|
.uri(URI.create(baseUrl + path))
|
||||||
|
.header("content-type", "application/json")
|
||||||
|
.header("x-certd-token", getToken())
|
||||||
|
.POST(HttpRequest.BodyPublishers.ofString(bodyJson, StandardCharsets.UTF_8))
|
||||||
|
.build();
|
||||||
|
|
||||||
|
HttpResponse<String> response = HttpClient.newHttpClient().send(request, HttpResponse.BodyHandlers.ofString());
|
||||||
|
if (response.statusCode() < 200 || response.statusCode() >= 300) {
|
||||||
|
throw new IOException("HTTP " + response.statusCode() + ": " + response.body());
|
||||||
|
}
|
||||||
|
return response.body();
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getCert(String paramsJson) throws Exception {
|
||||||
|
return request("/api/v1/cert/get", paramsJson);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String jsonEscape(String value) {
|
||||||
|
StringBuilder escaped = new StringBuilder();
|
||||||
|
for (int i = 0; i < value.length(); i++) {
|
||||||
|
char ch = value.charAt(i);
|
||||||
|
switch (ch) {
|
||||||
|
case '"':
|
||||||
|
escaped.append("\\\"");
|
||||||
|
break;
|
||||||
|
case '\\':
|
||||||
|
escaped.append("\\\\");
|
||||||
|
break;
|
||||||
|
case '\b':
|
||||||
|
escaped.append("\\b");
|
||||||
|
break;
|
||||||
|
case '\f':
|
||||||
|
escaped.append("\\f");
|
||||||
|
break;
|
||||||
|
case '\n':
|
||||||
|
escaped.append("\\n");
|
||||||
|
break;
|
||||||
|
case '\r':
|
||||||
|
escaped.append("\\r");
|
||||||
|
break;
|
||||||
|
case '\t':
|
||||||
|
escaped.append("\\t");
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
if (ch < 0x20) {
|
||||||
|
escaped.append(String.format("\\u%04x", (int) ch));
|
||||||
|
} else {
|
||||||
|
escaped.append(ch);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return escaped.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String md5Hex(String value) throws Exception {
|
||||||
|
MessageDigest md = MessageDigest.getInstance("MD5");
|
||||||
|
byte[] digest = md.digest(value.getBytes(StandardCharsets.UTF_8));
|
||||||
|
StringBuilder hex = new StringBuilder();
|
||||||
|
for (byte b : digest) {
|
||||||
|
hex.append(String.format("%02x", b));
|
||||||
|
}
|
||||||
|
return hex.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String base64(String value) {
|
||||||
|
return Base64.getEncoder().encodeToString(value.getBytes(StandardCharsets.UTF_8));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean isBlank(String value) {
|
||||||
|
return value == null || value.isBlank();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String trimRightSlash(String value) {
|
||||||
|
return value.replaceAll("/+$", "");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,84 @@
|
|||||||
|
public class GetCert {
|
||||||
|
public static void main(String[] args) {
|
||||||
|
try {
|
||||||
|
new GetCert().run();
|
||||||
|
} catch (Exception e) {
|
||||||
|
System.err.println(e.getMessage());
|
||||||
|
System.exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void run() throws Exception {
|
||||||
|
CertdClient client = new CertdClient(
|
||||||
|
requireEnv("CERTD_KEY_ID"),
|
||||||
|
requireEnv("CERTD_KEY_SECRET"),
|
||||||
|
env("CERTD_BASE_URL", "http://127.0.0.1:7001"),
|
||||||
|
boolEnv("CERTD_ENCRYPT", false)
|
||||||
|
);
|
||||||
|
|
||||||
|
String certId = System.getenv("CERTD_CERT_ID");
|
||||||
|
String domains = System.getenv("CERTD_DOMAINS");
|
||||||
|
String format = System.getenv("CERTD_FORMAT");
|
||||||
|
|
||||||
|
if (isBlank(certId) && isBlank(domains)) {
|
||||||
|
throw new IllegalArgumentException("Set CERTD_CERT_ID or CERTD_DOMAINS");
|
||||||
|
}
|
||||||
|
|
||||||
|
StringBuilder body = new StringBuilder();
|
||||||
|
body.append("{");
|
||||||
|
boolean hasField = false;
|
||||||
|
if (!isBlank(certId)) {
|
||||||
|
body.append("\"certId\":").append(Long.parseLong(certId));
|
||||||
|
hasField = true;
|
||||||
|
}
|
||||||
|
if (!isBlank(domains)) {
|
||||||
|
appendComma(body, hasField);
|
||||||
|
body.append("\"domains\":\"").append(CertdClient.jsonEscape(domains)).append("\"");
|
||||||
|
hasField = true;
|
||||||
|
}
|
||||||
|
appendComma(body, hasField);
|
||||||
|
body.append("\"autoApply\":").append(boolEnv("CERTD_AUTO_APPLY", false));
|
||||||
|
hasField = true;
|
||||||
|
if (!isBlank(format)) {
|
||||||
|
appendComma(body, hasField);
|
||||||
|
body.append("\"format\":\"").append(CertdClient.jsonEscape(format)).append("\"");
|
||||||
|
}
|
||||||
|
body.append("}");
|
||||||
|
|
||||||
|
System.out.println(client.getCert(body.toString()));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String requireEnv(String name) {
|
||||||
|
String value = System.getenv(name);
|
||||||
|
if (isBlank(value)) {
|
||||||
|
throw new IllegalArgumentException("Missing environment variable: " + name);
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String env(String name, String defaultValue) {
|
||||||
|
String value = System.getenv(name);
|
||||||
|
return isBlank(value) ? defaultValue : value;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean boolEnv(String name, boolean defaultValue) {
|
||||||
|
String value = System.getenv(name);
|
||||||
|
if (isBlank(value)) {
|
||||||
|
return defaultValue;
|
||||||
|
}
|
||||||
|
return value.equalsIgnoreCase("1")
|
||||||
|
|| value.equalsIgnoreCase("true")
|
||||||
|
|| value.equalsIgnoreCase("yes")
|
||||||
|
|| value.equalsIgnoreCase("y");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean isBlank(String value) {
|
||||||
|
return value == null || value.isBlank();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void appendComma(StringBuilder builder, boolean hasField) {
|
||||||
|
if (hasField) {
|
||||||
|
builder.append(",");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
const crypto = require("crypto");
|
||||||
|
|
||||||
|
class CertdClient {
|
||||||
|
constructor(keyId, keySecret, options = {}) {
|
||||||
|
if (!keyId) {
|
||||||
|
throw new Error("keyId is required");
|
||||||
|
}
|
||||||
|
if (!keySecret) {
|
||||||
|
throw new Error("keySecret is required");
|
||||||
|
}
|
||||||
|
this.keyId = keyId;
|
||||||
|
this.keySecret = keySecret;
|
||||||
|
this.baseUrl = (options.baseUrl || "http://127.0.0.1:7001").replace(/\/$/, "");
|
||||||
|
this.encrypt = options.encrypt === true;
|
||||||
|
this.signType = options.signType || "md5";
|
||||||
|
}
|
||||||
|
|
||||||
|
getSign(content) {
|
||||||
|
if (this.signType !== "md5") {
|
||||||
|
throw new Error(`Unsupported signType: ${this.signType}`);
|
||||||
|
}
|
||||||
|
return crypto.createHash("md5").update(content + this.keySecret, "utf8").digest("hex");
|
||||||
|
}
|
||||||
|
|
||||||
|
getToken(options = {}) {
|
||||||
|
const encrypt = options.encrypt ?? this.encrypt;
|
||||||
|
const content = JSON.stringify({
|
||||||
|
keyId: this.keyId,
|
||||||
|
t: Math.floor(Date.now() / 1000),
|
||||||
|
encrypt,
|
||||||
|
signType: this.signType,
|
||||||
|
});
|
||||||
|
const sign = this.getSign(content);
|
||||||
|
return `${Buffer.from(content, "utf8").toString("base64")}.${Buffer.from(sign, "utf8").toString("base64")}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
async request(path, body = {}, options = {}) {
|
||||||
|
const response = await fetch(`${this.baseUrl}${path}`, {
|
||||||
|
method: options.method || "POST",
|
||||||
|
headers: {
|
||||||
|
"content-type": "application/json",
|
||||||
|
"x-certd-token": this.getToken({ encrypt: options.encrypt }),
|
||||||
|
...(options.headers || {}),
|
||||||
|
},
|
||||||
|
body: JSON.stringify(body),
|
||||||
|
});
|
||||||
|
|
||||||
|
const text = await response.text();
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`HTTP ${response.status}: ${text}`);
|
||||||
|
}
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
|
||||||
|
getCert(params) {
|
||||||
|
return this.request("/api/v1/cert/get", params);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = { CertdClient };
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
#!/usr/bin/env node
|
||||||
|
|
||||||
|
const { CertdClient } = require("./certd-client");
|
||||||
|
|
||||||
|
function requireEnv(name) {
|
||||||
|
const value = process.env[name];
|
||||||
|
if (!value) {
|
||||||
|
throw new Error(`Missing environment variable: ${name}`);
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
function boolEnv(name, defaultValue = false) {
|
||||||
|
const value = process.env[name];
|
||||||
|
if (value == null || value === "") {
|
||||||
|
return defaultValue;
|
||||||
|
}
|
||||||
|
return ["1", "true", "yes", "y"].includes(value.toLowerCase());
|
||||||
|
}
|
||||||
|
|
||||||
|
async function main() {
|
||||||
|
const client = new CertdClient(requireEnv("CERTD_KEY_ID"), requireEnv("CERTD_KEY_SECRET"), {
|
||||||
|
baseUrl: process.env.CERTD_BASE_URL,
|
||||||
|
encrypt: boolEnv("CERTD_ENCRYPT"),
|
||||||
|
});
|
||||||
|
|
||||||
|
const params = {
|
||||||
|
autoApply: boolEnv("CERTD_AUTO_APPLY"),
|
||||||
|
};
|
||||||
|
if (process.env.CERTD_CERT_ID) {
|
||||||
|
params.certId = Number(process.env.CERTD_CERT_ID);
|
||||||
|
if (!Number.isInteger(params.certId) || params.certId <= 0) {
|
||||||
|
throw new Error("CERTD_CERT_ID must be a positive integer");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (process.env.CERTD_DOMAINS) {
|
||||||
|
params.domains = process.env.CERTD_DOMAINS;
|
||||||
|
}
|
||||||
|
if (process.env.CERTD_FORMAT) {
|
||||||
|
params.format = process.env.CERTD_FORMAT;
|
||||||
|
}
|
||||||
|
if (!params.certId && !params.domains) {
|
||||||
|
throw new Error("Set CERTD_CERT_ID or CERTD_DOMAINS");
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(await client.getCert(params));
|
||||||
|
}
|
||||||
|
|
||||||
|
main().catch(error => {
|
||||||
|
console.error(error.message);
|
||||||
|
process.exit(1);
|
||||||
|
});
|
||||||
@@ -0,0 +1,84 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
class CertdClient
|
||||||
|
{
|
||||||
|
private string $keyId;
|
||||||
|
private string $keySecret;
|
||||||
|
private string $baseUrl;
|
||||||
|
private bool $encrypt;
|
||||||
|
private string $signType;
|
||||||
|
|
||||||
|
public function __construct(string $keyId, string $keySecret, array $options = [])
|
||||||
|
{
|
||||||
|
if ($keyId === '') {
|
||||||
|
throw new InvalidArgumentException('keyId is required');
|
||||||
|
}
|
||||||
|
if ($keySecret === '') {
|
||||||
|
throw new InvalidArgumentException('keySecret is required');
|
||||||
|
}
|
||||||
|
$this->keyId = $keyId;
|
||||||
|
$this->keySecret = $keySecret;
|
||||||
|
$this->baseUrl = rtrim($options['baseUrl'] ?? 'http://127.0.0.1:7001', '/');
|
||||||
|
$this->encrypt = $options['encrypt'] ?? false;
|
||||||
|
$this->signType = $options['signType'] ?? 'md5';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getSign(string $content): string
|
||||||
|
{
|
||||||
|
if ($this->signType !== 'md5') {
|
||||||
|
throw new InvalidArgumentException("Unsupported signType: {$this->signType}");
|
||||||
|
}
|
||||||
|
return md5($content . $this->keySecret);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getToken(?bool $encrypt = null): string
|
||||||
|
{
|
||||||
|
$content = json_encode([
|
||||||
|
'keyId' => $this->keyId,
|
||||||
|
't' => time(),
|
||||||
|
'encrypt' => $encrypt ?? $this->encrypt,
|
||||||
|
'signType' => $this->signType,
|
||||||
|
], JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
|
||||||
|
$sign = $this->getSign($content);
|
||||||
|
return base64_encode($content) . '.' . base64_encode($sign);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function request(string $path, array $body = [], ?bool $encrypt = null): string
|
||||||
|
{
|
||||||
|
if (!function_exists('curl_init')) {
|
||||||
|
throw new RuntimeException('PHP curl extension is required');
|
||||||
|
}
|
||||||
|
|
||||||
|
$payload = json_encode($body, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
|
||||||
|
$headers = [
|
||||||
|
'Content-Type: application/json',
|
||||||
|
'x-certd-token: ' . $this->getToken($encrypt),
|
||||||
|
];
|
||||||
|
|
||||||
|
$ch = curl_init($this->baseUrl . $path);
|
||||||
|
curl_setopt_array($ch, [
|
||||||
|
CURLOPT_POST => true,
|
||||||
|
CURLOPT_POSTFIELDS => $payload,
|
||||||
|
CURLOPT_HTTPHEADER => $headers,
|
||||||
|
CURLOPT_RETURNTRANSFER => true,
|
||||||
|
CURLOPT_TIMEOUT => 60,
|
||||||
|
]);
|
||||||
|
|
||||||
|
$response = curl_exec($ch);
|
||||||
|
if ($response === false) {
|
||||||
|
throw new RuntimeException(curl_error($ch));
|
||||||
|
}
|
||||||
|
$statusCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||||
|
curl_close($ch);
|
||||||
|
|
||||||
|
if ($statusCode < 200 || $statusCode >= 300) {
|
||||||
|
throw new RuntimeException("HTTP {$statusCode}: {$response}");
|
||||||
|
}
|
||||||
|
return $response;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getCert(array $params): string
|
||||||
|
{
|
||||||
|
return $this->request('/api/v1/cert/get', $params);
|
||||||
|
}
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user