mirror of
https://github.com/certd/certd.git
synced 2026-08-08 23:49:36 +08:00
Compare commits
7
Commits
90ba55c043
...
v1.40.0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5801f34b3a | ||
|
|
17cf16ca92 | ||
|
|
7015b1b232 | ||
|
|
3b72ca09c6 | ||
|
|
a815d0245b | ||
|
|
229f22d5a9 | ||
|
|
22f5cfcfd8 |
@@ -105,7 +105,7 @@ Certd 是一个支持私有化部署的 SSL/TLS 证书自动化管理平台。
|
||||
|
||||
- 前端 `pnpm dev`:启动 Vite 开发服务
|
||||
- 前端 `pnpm build`:生产构建
|
||||
- 前端 `pnpm tsc`:类型检查
|
||||
- 不要运行前端 `pnpm tsc` / `vue-tsc`:当前依赖组合中 `vue-tsc@1.8.27` 会直接抛内部错误 `Search string not found: "/supportedTSExtensions = .*(?=;)/"`,不是有效的项目类型检查结果。
|
||||
- 前端暂不跑单元测试;当前 `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` 文件。
|
||||
- 单元测试需要 mock ESM 静态 import 时,优先使用 `esmock`,不要为了测试把业务代码改成构造函数注入或把逻辑挪到调用方;各包 `test:unit` 脚本应显式设置 `NODE_ENV=unittest`。
|
||||
- 单个 monorepo 包运行单元测试时,优先使用 `corepack pnpm --dir <包目录> test:unit`,例如 `corepack pnpm --dir packages\ui\certd-server test:unit`、`corepack pnpm --dir packages\core\basic test:unit`、`corepack pnpm --dir packages\plugins\plugin-lib test:unit`;也可以用包名过滤,例如 `corepack pnpm --filter @certd/ui-server test:unit`。前端 `packages\ui\certd-client` 暂时不跑单元测试。
|
||||
- 前端 TS/Vue/locale 等文件改动后,优先只对本次改动文件运行项目现有自动格式化/修复;Windows/PowerShell 下 Prettier 已验证可用命令为 `packages\ui\certd-client\node_modules\.bin\prettier.cmd --write <files>`,ESLint 可用命令为 `packages\ui\certd-client\node_modules\.bin\eslint.cmd --fix <files>`;不要为了格式化无关文件而扩大 diff。项目保留了 `tslint` 依赖,但当前主要使用 ESLint + Prettier。
|
||||
- 优先对改动包运行聚焦的测试或类型检查;只有跨包影响明显时再考虑全 monorepo 构建。
|
||||
- 前端 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。
|
||||
- 优先对改动包运行聚焦的测试;后端可按包运行单元测试,前端优先使用 Prettier/ESLint 做改动文件验证。只有跨包影响明显时再考虑全 monorepo 构建。
|
||||
|
||||
@@ -3,6 +3,25 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
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
|
||||
|
||||
@@ -38,6 +38,7 @@ Certd® 是一个免费的全自动证书管理系统,让你的网站证书永
|
||||
* **开放接口支持**: 提供RESTful API接口,方便集成到其他系统
|
||||
* **站点证书监控**: 定时监控网站证书的过期时间
|
||||
* **多用户管理**: 用户可以管理自己的证书流水线
|
||||
* **项目管理**: 企业级项目管理模式
|
||||
* **多语言支持**: 中英双语切换
|
||||
* **无忧升级**: 版本向下兼容
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -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)
|
||||
|
||||
|
||||
## 自动升级配置
|
||||
|
||||
### 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",
|
||||
"version": "1.39.16"
|
||||
"version": "1.40.0"
|
||||
}
|
||||
|
||||
@@ -3,6 +3,10 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
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
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
"description": "Simple and unopinionated ACME client",
|
||||
"private": false,
|
||||
"author": "nmorsman",
|
||||
"version": "1.39.16",
|
||||
"version": "1.40.0",
|
||||
"type": "module",
|
||||
"module": "./dist/index.js",
|
||||
"main": "./dist/index.js",
|
||||
@@ -18,7 +18,7 @@
|
||||
"types"
|
||||
],
|
||||
"dependencies": {
|
||||
"@certd/basic": "^1.39.16",
|
||||
"@certd/basic": "^1.40.0",
|
||||
"@peculiar/x509": "^1.11.0",
|
||||
"asn1js": "^3.0.5",
|
||||
"axios": "^1.9.0",
|
||||
|
||||
@@ -3,6 +3,10 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
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 +1 @@
|
||||
14:13
|
||||
00:44
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@certd/basic",
|
||||
"private": false,
|
||||
"version": "1.39.16",
|
||||
"version": "1.40.0",
|
||||
"type": "module",
|
||||
"main": "./dist/index.js",
|
||||
"module": "./dist/index.js",
|
||||
|
||||
@@ -3,6 +3,10 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
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,7 +1,7 @@
|
||||
{
|
||||
"name": "@certd/pipeline",
|
||||
"private": false,
|
||||
"version": "1.39.16",
|
||||
"version": "1.40.0",
|
||||
"type": "module",
|
||||
"main": "./dist/index.js",
|
||||
"module": "./dist/index.js",
|
||||
@@ -19,8 +19,8 @@
|
||||
"compile": "tsc --skipLibCheck --watch"
|
||||
},
|
||||
"dependencies": {
|
||||
"@certd/basic": "^1.39.16",
|
||||
"@certd/plus-core": "^1.39.16",
|
||||
"@certd/basic": "^1.40.0",
|
||||
"@certd/plus-core": "^1.40.0",
|
||||
"dayjs": "^1.11.7",
|
||||
"lodash-es": "^4.17.21",
|
||||
"reflect-metadata": "^0.1.13"
|
||||
|
||||
@@ -3,6 +3,10 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
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,7 +1,7 @@
|
||||
{
|
||||
"name": "@certd/lib-huawei",
|
||||
"private": false,
|
||||
"version": "1.39.16",
|
||||
"version": "1.40.0",
|
||||
"main": "./dist/bundle.js",
|
||||
"module": "./dist/bundle.js",
|
||||
"types": "./dist/d/index.d.ts",
|
||||
|
||||
@@ -3,6 +3,10 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
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,7 +1,7 @@
|
||||
{
|
||||
"name": "@certd/lib-iframe",
|
||||
"private": false,
|
||||
"version": "1.39.16",
|
||||
"version": "1.40.0",
|
||||
"type": "module",
|
||||
"main": "./dist/index.js",
|
||||
"module": "./dist/index.js",
|
||||
|
||||
@@ -3,6 +3,10 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
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,6 +1,6 @@
|
||||
{
|
||||
"name": "@certd/jdcloud",
|
||||
"version": "1.39.16",
|
||||
"version": "1.40.0",
|
||||
"description": "jdcloud openApi sdk",
|
||||
"main": "./dist/bundle.js",
|
||||
"module": "./dist/bundle.js",
|
||||
|
||||
@@ -3,6 +3,10 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
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,7 +1,7 @@
|
||||
{
|
||||
"name": "@certd/lib-k8s",
|
||||
"private": false,
|
||||
"version": "1.39.16",
|
||||
"version": "1.40.0",
|
||||
"type": "module",
|
||||
"main": "./dist/index.js",
|
||||
"module": "./dist/index.js",
|
||||
@@ -19,7 +19,7 @@
|
||||
"compile": "tsc --skipLibCheck --watch"
|
||||
},
|
||||
"dependencies": {
|
||||
"@certd/basic": "^1.39.16",
|
||||
"@certd/basic": "^1.40.0",
|
||||
"@kubernetes/client-node": "0.21.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
@@ -3,6 +3,12 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
# [1.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,6 +1,6 @@
|
||||
{
|
||||
"name": "@certd/lib-server",
|
||||
"version": "1.39.16",
|
||||
"version": "1.40.0",
|
||||
"description": "midway with flyway, sql upgrade way ",
|
||||
"private": false,
|
||||
"type": "module",
|
||||
@@ -29,11 +29,11 @@
|
||||
],
|
||||
"license": "AGPL",
|
||||
"dependencies": {
|
||||
"@certd/acme-client": "^1.39.16",
|
||||
"@certd/basic": "^1.39.16",
|
||||
"@certd/pipeline": "^1.39.16",
|
||||
"@certd/plugin-lib": "^1.39.16",
|
||||
"@certd/plus-core": "^1.39.16",
|
||||
"@certd/acme-client": "^1.40.0",
|
||||
"@certd/basic": "^1.40.0",
|
||||
"@certd/pipeline": "^1.40.0",
|
||||
"@certd/plugin-lib": "^1.40.0",
|
||||
"@certd/plus-core": "^1.40.0",
|
||||
"@midwayjs/cache": "3.14.0",
|
||||
"@midwayjs/core": "3.20.11",
|
||||
"@midwayjs/i18n": "3.20.13",
|
||||
|
||||
@@ -3,6 +3,10 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
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,6 +1,6 @@
|
||||
{
|
||||
"name": "@certd/midway-flyway-js",
|
||||
"version": "1.39.16",
|
||||
"version": "1.40.0",
|
||||
"description": "midway with flyway, sql upgrade way ",
|
||||
"private": false,
|
||||
"type": "module",
|
||||
|
||||
@@ -3,6 +3,10 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
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,7 +1,7 @@
|
||||
{
|
||||
"name": "@certd/plugin-cert",
|
||||
"private": false,
|
||||
"version": "1.39.16",
|
||||
"version": "1.40.0",
|
||||
"type": "module",
|
||||
"main": "./dist/index.js",
|
||||
"types": "./dist/index.d.ts",
|
||||
@@ -18,10 +18,10 @@
|
||||
"compile": "tsc --skipLibCheck --watch"
|
||||
},
|
||||
"dependencies": {
|
||||
"@certd/acme-client": "^1.39.16",
|
||||
"@certd/basic": "^1.39.16",
|
||||
"@certd/pipeline": "^1.39.16",
|
||||
"@certd/plugin-lib": "^1.39.16",
|
||||
"@certd/acme-client": "^1.40.0",
|
||||
"@certd/basic": "^1.40.0",
|
||||
"@certd/pipeline": "^1.40.0",
|
||||
"@certd/plugin-lib": "^1.40.0",
|
||||
"psl": "^1.9.0",
|
||||
"punycode.js": "^2.3.1"
|
||||
},
|
||||
|
||||
@@ -3,6 +3,10 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
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,7 +1,7 @@
|
||||
{
|
||||
"name": "@certd/plugin-lib",
|
||||
"private": false,
|
||||
"version": "1.39.16",
|
||||
"version": "1.40.0",
|
||||
"type": "module",
|
||||
"main": "./dist/index.js",
|
||||
"types": "./dist/index.d.ts",
|
||||
@@ -23,10 +23,10 @@
|
||||
"@alicloud/pop-core": "^1.7.10",
|
||||
"@alicloud/tea-util": "^1.4.11",
|
||||
"@aws-sdk/client-s3": "^3.964.0",
|
||||
"@certd/acme-client": "^1.39.16",
|
||||
"@certd/basic": "^1.39.16",
|
||||
"@certd/pipeline": "^1.39.16",
|
||||
"@certd/plus-core": "^1.39.16",
|
||||
"@certd/acme-client": "^1.40.0",
|
||||
"@certd/basic": "^1.40.0",
|
||||
"@certd/pipeline": "^1.40.0",
|
||||
"@certd/plus-core": "^1.40.0",
|
||||
"@kubernetes/client-node": "0.21.0",
|
||||
"ali-oss": "^6.22.0",
|
||||
"basic-ftp": "^5.0.5",
|
||||
|
||||
@@ -3,6 +3,22 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
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,6 +1,6 @@
|
||||
{
|
||||
"name": "@certd/ui-client",
|
||||
"version": "1.39.16",
|
||||
"version": "1.40.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "vite --open",
|
||||
@@ -106,8 +106,8 @@
|
||||
"zod-defaults": "^0.1.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@certd/lib-iframe": "^1.39.16",
|
||||
"@certd/pipeline": "^1.39.16",
|
||||
"@certd/lib-iframe": "^1.40.0",
|
||||
"@certd/pipeline": "^1.40.0",
|
||||
"@rollup/plugin-commonjs": "^25.0.7",
|
||||
"@rollup/plugin-node-resolve": "^15.2.3",
|
||||
"@types/chai": "^4.3.12",
|
||||
|
||||
@@ -78,8 +78,13 @@ export default {
|
||||
passkeyRegisterFailed: "Passkey registration failed",
|
||||
title: "Change Password",
|
||||
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",
|
||||
setNow: "Set Now",
|
||||
notNow: "Not Now",
|
||||
successMessage: "Changed successfully",
|
||||
initPasswordSuccessMessage: "Set successfully",
|
||||
oldPassword: "Old Password",
|
||||
oldPasswordRequired: "Please enter the old password",
|
||||
newPassword: "New Password",
|
||||
|
||||
@@ -79,8 +79,13 @@ export default {
|
||||
|
||||
title: "修改密码",
|
||||
weakPasswordWarning: "为了您的账户安全,请立即修改密码",
|
||||
initPasswordWarning: "当前账号还未设置登录密码,请先设置密码",
|
||||
initPasswordTitle: "设置密码",
|
||||
changeNow: "立即修改",
|
||||
setNow: "立即设置",
|
||||
notNow: "暂不设置",
|
||||
successMessage: "修改成功",
|
||||
initPasswordSuccessMessage: "设置成功",
|
||||
oldPassword: "旧密码",
|
||||
oldPasswordRequired: "请输入旧密码",
|
||||
newPassword: "新密码",
|
||||
|
||||
@@ -38,6 +38,7 @@ export interface UserInfoRes {
|
||||
avatar?: string;
|
||||
roleIds: number[];
|
||||
isWeak?: boolean;
|
||||
needInitPassword?: boolean;
|
||||
validTime?: number;
|
||||
status?: number;
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
return await request({
|
||||
url: "/mine/updateProfile",
|
||||
|
||||
@@ -9,7 +9,7 @@ import { ref } from "vue";
|
||||
import { useI18n } from "/src/locales";
|
||||
|
||||
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 { notification } from "ant-design-vue";
|
||||
import { useUserStore } from "/@/store/user";
|
||||
@@ -20,6 +20,11 @@ defineProps<{
|
||||
|
||||
let passwordFormRef = ref();
|
||||
|
||||
type OpenOptions = {
|
||||
password?: string;
|
||||
init?: boolean;
|
||||
};
|
||||
|
||||
const validatePass1 = async (rule: any, value: any) => {
|
||||
if (value === "") {
|
||||
throw new Error(t("authentication.enterPassword"));
|
||||
@@ -53,19 +58,33 @@ const passwordFormOptions: CrudOptions = {
|
||||
width: "500px",
|
||||
},
|
||||
async doSubmit({ form }) {
|
||||
await api.changePassword(form);
|
||||
if (form.init) {
|
||||
await api.initPassword(form);
|
||||
} else {
|
||||
await api.changePassword(form);
|
||||
}
|
||||
//重新加载用户信息
|
||||
await userStore.loadUserInfo();
|
||||
},
|
||||
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: {
|
||||
init: {
|
||||
title: "init",
|
||||
type: "text",
|
||||
form: {
|
||||
show: false,
|
||||
},
|
||||
},
|
||||
password: {
|
||||
title: t("authentication.oldPassword"),
|
||||
type: "password",
|
||||
form: {
|
||||
show: compute(({ form }) => form.init !== true),
|
||||
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);
|
||||
formOptions.newInstance = true; //新实例打开
|
||||
if (opts.init) {
|
||||
formOptions.wrapper.title = t("authentication.initPasswordTitle");
|
||||
}
|
||||
passwordFormRef.value = await openDialog(formOptions);
|
||||
passwordFormRef.value.setFormData({
|
||||
password: opts.password,
|
||||
init: opts.init === true,
|
||||
password: opts.password || "",
|
||||
});
|
||||
console.log(passwordFormRef.value);
|
||||
}
|
||||
|
||||
@@ -48,7 +48,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<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">
|
||||
{{ t("authentication.securitySettingTip") }}
|
||||
@@ -387,6 +387,7 @@ const checkPasskeySupport = () => {
|
||||
}
|
||||
};
|
||||
const userStore = useUserStore();
|
||||
const changePasswordButtonRef = ref();
|
||||
const userAvatar = computed(() => {
|
||||
if (isEmpty(userInfo.value.avatar)) {
|
||||
return "";
|
||||
@@ -395,11 +396,26 @@ const userAvatar = computed(() => {
|
||||
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 () => {
|
||||
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 loadOauthBounds();
|
||||
await loadOauthProviders();
|
||||
|
||||
@@ -3,6 +3,23 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
# [1.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
|
||||
|
||||
@@ -39,6 +39,8 @@ input:
|
||||
component:
|
||||
name: a-switch
|
||||
vModel: checked
|
||||
col:
|
||||
span: 24
|
||||
helper: 是否启用了双重认证
|
||||
required: true
|
||||
deviceId:
|
||||
@@ -48,6 +50,8 @@ input:
|
||||
name: synology-device-id-getter
|
||||
type: access
|
||||
typeName: synology
|
||||
col:
|
||||
span: 24
|
||||
mergeScript: |2-
|
||||
|
||||
return {
|
||||
|
||||
@@ -21,28 +21,28 @@ input:
|
||||
options:
|
||||
- label: QQ
|
||||
value: qq
|
||||
icon: logos:tencent-qq
|
||||
icon: cib:tencent-qq:#007AFF
|
||||
- label: 微信
|
||||
value: wx
|
||||
icon: logos:wechat-icon
|
||||
icon: simple-icons:wechat:#34C759
|
||||
- label: 支付宝
|
||||
value: alipay
|
||||
icon: logos:alipay
|
||||
icon: simple-icons:alipay:#0099ff
|
||||
- label: 微博
|
||||
value: sina
|
||||
icon: logos:sina-weibo
|
||||
icon: uiw:weibo:#FF3B30
|
||||
- label: 百度
|
||||
value: baidu
|
||||
icon: logos:baidu
|
||||
icon: simple-icons:baidu:#007AFF
|
||||
- label: 华为
|
||||
value: huawei
|
||||
icon: simple-icons:huawei:#ff0000
|
||||
- label: 小米
|
||||
value: xiaomi
|
||||
icon: logos:xiaomi-icon
|
||||
icon: simple-icons:xiaomi:#FF9500
|
||||
- label: 谷歌
|
||||
value: google
|
||||
icon: logos:google-icon
|
||||
icon: flat-color-icons:google
|
||||
- label: 微软
|
||||
value: microsoft
|
||||
icon: logos:microsoft-icon
|
||||
@@ -54,7 +54,7 @@ input:
|
||||
icon: logos:twitter
|
||||
- label: 钉钉
|
||||
value: dingtalk
|
||||
icon: logos:dingtalk
|
||||
icon: ant-design:dingding-outlined:#007AFF
|
||||
- label: Gitee
|
||||
value: gitee
|
||||
icon: simple-icons:gitee:#c71d23
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@certd/ui-server",
|
||||
"version": "1.39.16",
|
||||
"version": "1.40.0",
|
||||
"description": "fast-server base midway",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
@@ -53,20 +53,20 @@
|
||||
"@aws-sdk/client-sts": "^3.990.0",
|
||||
"@azure/arm-dns": "^5.1.0",
|
||||
"@azure/identity": "^4.13.1",
|
||||
"@certd/acme-client": "^1.39.16",
|
||||
"@certd/basic": "^1.39.16",
|
||||
"@certd/commercial-core": "^1.39.16",
|
||||
"@certd/acme-client": "^1.40.0",
|
||||
"@certd/basic": "^1.40.0",
|
||||
"@certd/commercial-core": "^1.40.0",
|
||||
"@certd/cv4pve-api-javascript": "^8.4.2",
|
||||
"@certd/jdcloud": "^1.39.16",
|
||||
"@certd/lib-huawei": "^1.39.16",
|
||||
"@certd/lib-k8s": "^1.39.16",
|
||||
"@certd/lib-server": "^1.39.16",
|
||||
"@certd/midway-flyway-js": "^1.39.16",
|
||||
"@certd/pipeline": "^1.39.16",
|
||||
"@certd/plugin-cert": "^1.39.16",
|
||||
"@certd/plugin-lib": "^1.39.16",
|
||||
"@certd/plugin-plus": "^1.39.16",
|
||||
"@certd/plus-core": "^1.39.16",
|
||||
"@certd/jdcloud": "^1.40.0",
|
||||
"@certd/lib-huawei": "^1.40.0",
|
||||
"@certd/lib-k8s": "^1.40.0",
|
||||
"@certd/lib-server": "^1.40.0",
|
||||
"@certd/midway-flyway-js": "^1.40.0",
|
||||
"@certd/pipeline": "^1.40.0",
|
||||
"@certd/plugin-cert": "^1.40.0",
|
||||
"@certd/plugin-lib": "^1.40.0",
|
||||
"@certd/plugin-plus": "^1.40.0",
|
||||
"@certd/plus-core": "^1.40.0",
|
||||
"@google-cloud/dns": "^5.3.1",
|
||||
"@google-cloud/publicca": "^1.3.0",
|
||||
"@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 { setLogger } from '@certd/acme-client';
|
||||
import {HiddenMiddleware} from "./middleware/hidden.js";
|
||||
import { shouldSetDefaultNoCache } from './configuration-cache.js';
|
||||
// import * as swagger from '@midwayjs/swagger';
|
||||
//@ts-ignore
|
||||
// process.env.UV_THREADPOOL_SIZE = 2
|
||||
@@ -123,7 +124,7 @@ export class MainConfiguration {
|
||||
|
||||
this.app.getMiddleware().insertFirst(async (ctx: IMidwayKoaContext, next: NextFunction) => {
|
||||
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');
|
||||
}
|
||||
});
|
||||
|
||||
@@ -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 { 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()
|
||||
@@ -40,8 +59,10 @@ export class FileController extends BaseController {
|
||||
userId = this.getUserId();
|
||||
}
|
||||
const filePath = this.fileService.getFile(key, userId);
|
||||
this.ctx.response.attachment(filePath);
|
||||
this.ctx.response.set('Cache-Control', 'public,max-age=2592000');
|
||||
await send(this.ctx, filePath);
|
||||
const sendOptions = getImageDownloadOptions(filePath);
|
||||
if (!sendOptions) {
|
||||
this.ctx.response.attachment(filePath);
|
||||
}
|
||||
await send(this.ctx, filePath, sendOptions);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -82,14 +82,22 @@ export class ConnectController extends BaseController {
|
||||
const bindUrl = installInfo?.bindUrl || "";
|
||||
//构造登录url
|
||||
const redirectUrl = `${bindUrl}api/oauth/callback/${body.type}`;
|
||||
|
||||
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, {
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
import { BaseController, Constants, SysSettingsService } from '@certd/lib-server';
|
||||
import { ALL, Body, Controller, Inject, Post, Provide } from '@midwayjs/core';
|
||||
import { PasskeyService } from '../../../modules/login/service/passkey-service.js';
|
||||
import { RoleService } from '../../../modules/sys/authority/service/role-service.js';
|
||||
import { UserService } from '../../../modules/sys/authority/service/user-service.js';
|
||||
import { ApiTags } from '@midwayjs/swagger';
|
||||
import { CodeService } from '../../../modules/basic/service/code-service.js';
|
||||
import { BaseController, Constants, SysSettingsService } from "@certd/lib-server";
|
||||
import { ALL, Body, Controller, Inject, Post, Provide } from "@midwayjs/core";
|
||||
import { PasskeyService } from "../../../modules/login/service/passkey-service.js";
|
||||
import { RoleService } from "../../../modules/sys/authority/service/role-service.js";
|
||||
import { UserService } from "../../../modules/sys/authority/service/user-service.js";
|
||||
import { ApiTags } from "@midwayjs/swagger";
|
||||
import { CodeService } from "../../../modules/basic/service/code-service.js";
|
||||
|
||||
/**
|
||||
*/
|
||||
@Provide()
|
||||
@Controller('/api/mine')
|
||||
@ApiTags(['mine'])
|
||||
@Controller("/api/mine")
|
||||
@ApiTags(["mine"])
|
||||
export class MineController extends BaseController {
|
||||
@Inject()
|
||||
userService: UserService;
|
||||
@@ -27,28 +27,38 @@ export class MineController extends BaseController {
|
||||
@Inject()
|
||||
sysSettingsService: SysSettingsService;
|
||||
|
||||
@Post('/info', { description: Constants.per.authOnly, summary: '查询用户信息' })
|
||||
@Post("/info", { description: Constants.per.authOnly, summary: "查询用户信息" })
|
||||
public async info() {
|
||||
const userId = this.getUserId();
|
||||
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) {
|
||||
//@ts-ignore
|
||||
user.isWeak = true;
|
||||
}
|
||||
const needInitPassword = user.password === "changeme";
|
||||
user.roleIds = await this.roleService.getRoleIdsByUserId(userId);
|
||||
delete user.password;
|
||||
//@ts-ignore
|
||||
user.needInitPassword = needInitPassword;
|
||||
return this.ok(user);
|
||||
}
|
||||
|
||||
@Post('/changePassword', { description: Constants.per.authOnly, summary: '修改密码' })
|
||||
@Post("/changePassword", { description: Constants.per.authOnly, summary: "修改密码" })
|
||||
public async changePassword(@Body(ALL) body: any) {
|
||||
const userId = this.getUserId();
|
||||
await this.userService.changePassword(userId, body);
|
||||
return this.ok({});
|
||||
}
|
||||
|
||||
@Post('/updateProfile', { description: Constants.per.authOnly, summary: '更新用户资料' })
|
||||
@Post("/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) {
|
||||
const userId = this.getUserId();
|
||||
|
||||
@@ -59,7 +69,7 @@ export class MineController extends BaseController {
|
||||
return this.ok({});
|
||||
}
|
||||
|
||||
@Post('/contact/capability', { description: Constants.per.authOnly, summary: '查询联系方式绑定能力' })
|
||||
@Post("/contact/capability", { description: Constants.per.authOnly, summary: "查询联系方式绑定能力" })
|
||||
public async contactCapability() {
|
||||
const settings = await this.sysSettingsService.getPrivateSettings();
|
||||
return this.ok({
|
||||
@@ -67,27 +77,27 @@ export class MineController extends BaseController {
|
||||
});
|
||||
}
|
||||
|
||||
@Post('/contact/verifyIdentity', { description: Constants.per.authOnly, summary: '验证本人操作' })
|
||||
public async verifyContactIdentity(@Body(ALL) body: { identityType: 'password' | 'email' | 'mobile'; identityPassword?: string; identityValidateCode?: string }) {
|
||||
@Post("/contact/verifyIdentity", { description: Constants.per.authOnly, summary: "验证本人操作" })
|
||||
public async verifyContactIdentity(@Body(ALL) body: { identityType: "password" | "email" | "mobile"; identityPassword?: string; identityValidateCode?: string }) {
|
||||
const userId = this.getUserId();
|
||||
await this.userService.verifyIdentity(userId, body, this.codeService);
|
||||
const validationCode = this.codeService.setValidationValue({
|
||||
type: 'contactIdentity',
|
||||
type: "contactIdentity",
|
||||
userId,
|
||||
identityType: body.identityType,
|
||||
});
|
||||
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 }) {
|
||||
const userId = this.getUserId();
|
||||
this.userService.checkContactIdentityValidation(userId, body.identityValidationCode, this.codeService);
|
||||
await this.codeService.checkSmsCode({
|
||||
mobile: body.mobile,
|
||||
phoneCode: body.phoneCode || '86',
|
||||
phoneCode: body.phoneCode || "86",
|
||||
smsCode: body.validateCode,
|
||||
verificationType: 'bindMobile',
|
||||
verificationType: "bindMobile",
|
||||
throwError: true,
|
||||
});
|
||||
await this.userService.updateMobile(userId, {
|
||||
@@ -97,14 +107,14 @@ export class MineController extends BaseController {
|
||||
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 }) {
|
||||
const userId = this.getUserId();
|
||||
this.userService.checkContactIdentityValidation(userId, body.identityValidationCode, this.codeService);
|
||||
this.codeService.checkEmailCode({
|
||||
email: body.email,
|
||||
validateCode: body.validateCode,
|
||||
verificationType: 'bindEmail',
|
||||
verificationType: "bindEmail",
|
||||
throwError: true,
|
||||
});
|
||||
await this.userService.updateEmail(userId, {
|
||||
|
||||
@@ -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 { InjectEntityModel } from '@midwayjs/typeorm';
|
||||
import { EntityManager, In, MoreThan, Not, Repository } from 'typeorm';
|
||||
import { UserEntity } from '../entity/user.js';
|
||||
import * as _ from 'lodash-es';
|
||||
import { BaseService, CommonException, Constants, FileService, SysInstallInfo, SysSettingsService } from '@certd/lib-server';
|
||||
import { RoleService } from './role-service.js';
|
||||
import { PermissionService } from './permission-service.js';
|
||||
import { UserRoleService } from './user-role-service.js';
|
||||
import { UserRoleEntity } from '../entity/user-role.js';
|
||||
import bcrypt from 'bcryptjs';
|
||||
import { RandomUtil } from '../../../../utils/random.js';
|
||||
import dayjs from 'dayjs';
|
||||
import { DbAdapter } from '../../../db/index.js';
|
||||
import { simpleNanoId, utils } from '@certd/basic';
|
||||
import { OauthBoundService } from '../../../login/service/oauth-bound-service.js';
|
||||
import { Inject, Provide, Scope, ScopeEnum } from "@midwayjs/core";
|
||||
import { InjectEntityModel } from "@midwayjs/typeorm";
|
||||
import { EntityManager, In, MoreThan, Not, Repository } from "typeorm";
|
||||
import { UserEntity } from "../entity/user.js";
|
||||
import * as _ from "lodash-es";
|
||||
import { BaseService, CommonException, Constants, FileService, SysInstallInfo, SysSettingsService } from "@certd/lib-server";
|
||||
import { RoleService } from "./role-service.js";
|
||||
import { PermissionService } from "./permission-service.js";
|
||||
import { UserRoleService } from "./user-role-service.js";
|
||||
import { UserRoleEntity } from "../entity/user-role.js";
|
||||
import bcrypt from "bcryptjs";
|
||||
import { RandomUtil } from "../../../../utils/random.js";
|
||||
import dayjs from "dayjs";
|
||||
import { DbAdapter } from "../../../db/index.js";
|
||||
import { simpleNanoId, utils } from "@certd/basic";
|
||||
import { OauthBoundService } from "../../../login/service/oauth-bound-service.js";
|
||||
|
||||
export type RegisterType = 'username' | 'mobile' | 'email';
|
||||
export type ForgotPasswordType = 'mobile' | 'email';
|
||||
export type RegisterType = "username" | "mobile" | "email";
|
||||
export type ForgotPasswordType = "mobile" | "email";
|
||||
|
||||
export const AdminRoleId = 1;
|
||||
|
||||
@@ -83,14 +83,14 @@ export class UserService extends BaseService<UserEntity> {
|
||||
},
|
||||
});
|
||||
if (!_.isEmpty(exists)) {
|
||||
throw new CommonException('用户名已经存在');
|
||||
throw new CommonException("用户名已经存在");
|
||||
}
|
||||
const plainPassword = param.password ?? RandomUtil.randomStr(6);
|
||||
param.passwordVersion = 2;
|
||||
param.password = await this.genPassword(plainPassword, param.passwordVersion); // 默认密码 建议未改密码不能登陆
|
||||
|
||||
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);
|
||||
@@ -107,13 +107,13 @@ export class UserService extends BaseService<UserEntity> {
|
||||
*/
|
||||
async update(param) {
|
||||
if (param.id == null) {
|
||||
throw new CommonException('id不能为空');
|
||||
throw new CommonException("id不能为空");
|
||||
}
|
||||
const userInfo = await this.repository.findOne({
|
||||
where: { id: param.id },
|
||||
});
|
||||
if (!userInfo) {
|
||||
throw new CommonException('用户不存在');
|
||||
throw new CommonException("用户不存在");
|
||||
}
|
||||
|
||||
if (param.username) {
|
||||
@@ -125,7 +125,7 @@ export class UserService extends BaseService<UserEntity> {
|
||||
{ email: username, id: Not(id) },
|
||||
]);
|
||||
if (old != null) {
|
||||
throw new CommonException('用户名已被占用');
|
||||
throw new CommonException("用户名已被占用");
|
||||
}
|
||||
}
|
||||
if (!_.isEmpty(param.password)) {
|
||||
@@ -136,7 +136,7 @@ export class UserService extends BaseService<UserEntity> {
|
||||
}
|
||||
|
||||
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 this.roleService.updateRoles(param.id, param.roles);
|
||||
@@ -168,7 +168,7 @@ export class UserService extends BaseService<UserEntity> {
|
||||
async buildPlainPassword(rawPassword: string) {
|
||||
const setting: SysInstallInfo = await this.sysSettingsService.getSetting(SysInstallInfo);
|
||||
if (!setting.siteId) {
|
||||
throw new CommonException('站点ID还未初始化');
|
||||
throw new CommonException("站点ID还未初始化");
|
||||
}
|
||||
const prefixSiteId = setting.siteId.substring(1, 5);
|
||||
return rawPassword + prefixSiteId;
|
||||
@@ -184,54 +184,55 @@ export class UserService extends BaseService<UserEntity> {
|
||||
}
|
||||
|
||||
async register(type: string, user: UserEntity, withTx?: (tx: EntityManager) => Promise<void>) {
|
||||
if (!user.password) {
|
||||
user.password = simpleNanoId();
|
||||
}
|
||||
// if (!user.password) {
|
||||
// user.password = simpleNanoId();
|
||||
// }
|
||||
|
||||
if (user.username) {
|
||||
const username = user.username;
|
||||
const old = await this.findOne([{ username: username }, { mobile: username }, { email: username }]);
|
||||
if (old != null) {
|
||||
throw new CommonException('用户名已被注册');
|
||||
throw new CommonException("用户名已被注册");
|
||||
}
|
||||
}
|
||||
|
||||
if (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 }]);
|
||||
if (old != null) {
|
||||
throw new CommonException('手机号已被注册');
|
||||
throw new CommonException("手机号已被注册");
|
||||
}
|
||||
}
|
||||
if (user.email) {
|
||||
const email = user.email;
|
||||
const old = await this.findOne([{ username: email }, { mobile: email }, { email: email }]);
|
||||
if (old != null) {
|
||||
throw new CommonException('邮箱已被注册');
|
||||
throw new CommonException("邮箱已被注册");
|
||||
}
|
||||
}
|
||||
|
||||
if (!user.username) {
|
||||
user.username = 'user_' + simpleNanoId();
|
||||
user.username = "user_" + simpleNanoId();
|
||||
}
|
||||
|
||||
let newUser: UserEntity = UserEntity.of({
|
||||
username: user.username,
|
||||
password: user.password,
|
||||
email: user.email || '',
|
||||
mobile: user.mobile || '',
|
||||
email: user.email || "",
|
||||
mobile: user.mobile || "",
|
||||
nickName: user.nickName || user.username,
|
||||
avatar: user.avatar || '',
|
||||
phoneCode: user.phoneCode || '86',
|
||||
avatar: user.avatar || "",
|
||||
phoneCode: user.phoneCode || "86",
|
||||
status: 1,
|
||||
passwordVersion: 2,
|
||||
});
|
||||
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 => {
|
||||
newUser = await txManager.save(newUser);
|
||||
@@ -246,28 +247,28 @@ export class UserService extends BaseService<UserEntity> {
|
||||
|
||||
delete newUser.password;
|
||||
|
||||
utils.mitter.emit('register', { userId: newUser.id });
|
||||
utils.mitter.emit("register", { userId: newUser.id });
|
||||
|
||||
return newUser;
|
||||
}
|
||||
|
||||
async forgotPassword(data: { type: ForgotPasswordType; input?: string; phoneCode?: string; validateCode: string; password: string; confirmPassword: string }) {
|
||||
if (!data.type) {
|
||||
throw new CommonException('找回类型不能为空');
|
||||
throw new CommonException("找回类型不能为空");
|
||||
}
|
||||
if (data.password !== data.confirmPassword) {
|
||||
throw new CommonException('两次输入的密码不一致');
|
||||
throw new CommonException("两次输入的密码不一致");
|
||||
}
|
||||
const where: any = {
|
||||
[data.type]: data.input,
|
||||
};
|
||||
if (data.type === 'mobile') {
|
||||
where.phoneCode = data.phoneCode ?? '86';
|
||||
if (data.type === "mobile") {
|
||||
where.phoneCode = data.phoneCode ?? "86";
|
||||
}
|
||||
const user = await this.findOne({ [data.type]: data.input });
|
||||
console.log('user', user);
|
||||
console.log("user", user);
|
||||
if (!user) {
|
||||
throw new CommonException('用户不存在');
|
||||
throw new CommonException("用户不存在");
|
||||
// return;
|
||||
}
|
||||
await this.resetPassword(user.id, data.password);
|
||||
@@ -282,7 +283,7 @@ export class UserService extends BaseService<UserEntity> {
|
||||
const user = await this.info(userId);
|
||||
const passwordChecked = await this.checkPassword(form.password, user.password, user.passwordVersion);
|
||||
if (!passwordChecked) {
|
||||
throw new CommonException('原密码错误');
|
||||
throw new CommonException("原密码错误");
|
||||
}
|
||||
const param = {
|
||||
id: userId,
|
||||
@@ -292,9 +293,26 @@ export class UserService extends BaseService<UserEntity> {
|
||||
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) {
|
||||
if (!userId) {
|
||||
throw new CommonException('userId不能为空');
|
||||
throw new CommonException("userId不能为空");
|
||||
}
|
||||
const param = {
|
||||
id: userId,
|
||||
@@ -305,15 +323,15 @@ export class UserService extends BaseService<UserEntity> {
|
||||
|
||||
//@ts-ignore
|
||||
async delete(ids: any) {
|
||||
if (typeof ids === 'string') {
|
||||
ids = ids.split(',');
|
||||
if (typeof ids === "string") {
|
||||
ids = ids.split(",");
|
||||
ids = ids.map(id => parseInt(id));
|
||||
}
|
||||
if (ids.length === 0) {
|
||||
return;
|
||||
}
|
||||
if (ids.includes(1)) {
|
||||
throw new CommonException('不能删除管理员');
|
||||
throw new CommonException("不能删除管理员");
|
||||
}
|
||||
await super.delete(ids);
|
||||
await this.oauthBoundService.deleteWhere({
|
||||
@@ -323,7 +341,7 @@ export class UserService extends BaseService<UserEntity> {
|
||||
|
||||
async isAdmin(userId: any) {
|
||||
if (!userId) {
|
||||
throw new CommonException('userId不能为空');
|
||||
throw new CommonException("userId不能为空");
|
||||
}
|
||||
const userRoles = await this.userRoleService.find({
|
||||
where: {
|
||||
@@ -338,7 +356,7 @@ export class UserService extends BaseService<UserEntity> {
|
||||
|
||||
async updateStatus(id: number, status: number) {
|
||||
if (!id) {
|
||||
throw new CommonException('userId不能为空');
|
||||
throw new CommonException("userId不能为空");
|
||||
}
|
||||
await this.repository.update(id, {
|
||||
status,
|
||||
@@ -355,16 +373,16 @@ export class UserService extends BaseService<UserEntity> {
|
||||
}
|
||||
|
||||
async registerCountPerDay(param: { days: number } = { days: 7 }) {
|
||||
const todayEnd = dayjs().endOf('day');
|
||||
const todayEnd = dayjs().endOf("day");
|
||||
const result = await this.getRepository()
|
||||
.createQueryBuilder('main')
|
||||
.select(`${this.dbAdapter.date('main.createTime')} AS date`) // 将UNIX时间戳转换为日期
|
||||
.addSelect('COUNT(1) AS count')
|
||||
.createQueryBuilder("main")
|
||||
.select(`${this.dbAdapter.date("main.createTime")} AS date`) // 将UNIX时间戳转换为日期
|
||||
.addSelect("COUNT(1) AS count")
|
||||
.where({
|
||||
// 0点
|
||||
createTime: MoreThan(todayEnd.add(-param.days, 'day').toDate()),
|
||||
createTime: MoreThan(todayEnd.add(-param.days, "day").toDate()),
|
||||
})
|
||||
.groupBy('date')
|
||||
.groupBy("date")
|
||||
.getRawMany();
|
||||
|
||||
return result;
|
||||
@@ -384,7 +402,7 @@ export class UserService extends BaseService<UserEntity> {
|
||||
status: 1,
|
||||
},
|
||||
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);
|
||||
if (body.identityType === 'password') {
|
||||
if (body.identityType === "password") {
|
||||
const passwordChecked = await this.checkPassword(body.identityPassword, user.password, user.passwordVersion);
|
||||
if (!passwordChecked) {
|
||||
throw new CommonException('密码错误');
|
||||
throw new CommonException("密码错误");
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (body.identityType === 'email') {
|
||||
if (body.identityType === "email") {
|
||||
if (!user.email) {
|
||||
throw new CommonException('当前账号未绑定邮箱');
|
||||
throw new CommonException("当前账号未绑定邮箱");
|
||||
}
|
||||
codeService.checkEmailCode({
|
||||
email: user.email,
|
||||
validateCode: body.identityValidateCode,
|
||||
verificationType: 'contactIdentity',
|
||||
verificationType: "contactIdentity",
|
||||
throwError: true,
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (body.identityType === 'mobile') {
|
||||
if (body.identityType === "mobile") {
|
||||
if (!user.mobile) {
|
||||
throw new CommonException('当前账号未绑定手机号');
|
||||
throw new CommonException("当前账号未绑定手机号");
|
||||
}
|
||||
await codeService.checkSmsCode({
|
||||
mobile: user.mobile,
|
||||
phoneCode: user.phoneCode || '86',
|
||||
phoneCode: user.phoneCode || "86",
|
||||
smsCode: body.identityValidateCode,
|
||||
verificationType: 'contactIdentity',
|
||||
verificationType: "contactIdentity",
|
||||
throwError: true,
|
||||
});
|
||||
return;
|
||||
}
|
||||
throw new CommonException('不支持的验证方式');
|
||||
throw new CommonException("不支持的验证方式");
|
||||
}
|
||||
|
||||
checkContactIdentityValidation(userId: number, validationCode: string, codeService: any) {
|
||||
const validationValue = codeService.getValidationValue(validationCode);
|
||||
if (!validationValue || validationValue.type !== 'contactIdentity' || validationValue.userId !== userId) {
|
||||
throw new CommonException('请先验证本人操作');
|
||||
if (!validationValue || validationValue.type !== "contactIdentity" || validationValue.userId !== userId) {
|
||||
throw new CommonException("请先验证本人操作");
|
||||
}
|
||||
}
|
||||
|
||||
async updateMobile(userId: number, body: { phoneCode?: string; mobile: string }) {
|
||||
const mobile = body.mobile?.trim();
|
||||
if (!mobile) {
|
||||
throw new CommonException('手机号不能为空');
|
||||
throw new CommonException("手机号不能为空");
|
||||
}
|
||||
const old = await this.findOne(buildUserContactConflictWhere(mobile, userId));
|
||||
if (old != null) {
|
||||
throw new CommonException('手机号已被占用');
|
||||
throw new CommonException("手机号已被占用");
|
||||
}
|
||||
await this.repository.update(userId, {
|
||||
phoneCode: body.phoneCode || '86',
|
||||
phoneCode: body.phoneCode || "86",
|
||||
mobile,
|
||||
});
|
||||
}
|
||||
@@ -458,11 +476,11 @@ export class UserService extends BaseService<UserEntity> {
|
||||
async updateEmail(userId: number, body: { email: string }) {
|
||||
const email = body.email?.trim();
|
||||
if (!email) {
|
||||
throw new CommonException('邮箱不能为空');
|
||||
throw new CommonException("邮箱不能为空");
|
||||
}
|
||||
const old = await this.findOne(buildUserContactConflictWhere(email, userId));
|
||||
if (old != null) {
|
||||
throw new CommonException('邮箱已被占用');
|
||||
throw new CommonException("邮箱已被占用");
|
||||
}
|
||||
await this.repository.update(userId, {
|
||||
email,
|
||||
@@ -471,7 +489,7 @@ export class UserService extends BaseService<UserEntity> {
|
||||
|
||||
async getAllUserIds() {
|
||||
const users = await this.repository.find({
|
||||
select: ['id'],
|
||||
select: ["id"],
|
||||
where: {
|
||||
status: 1,
|
||||
},
|
||||
|
||||
@@ -42,6 +42,7 @@ export type BuildLoginUrlReq = {
|
||||
forType?: string;
|
||||
from?:string;
|
||||
subtype?: string;
|
||||
state?: string;
|
||||
}
|
||||
|
||||
export type BuildLogoutUrlReq = {
|
||||
|
||||
@@ -94,16 +94,14 @@ export class CloginOauthProvider extends BaseAddon implements IOauthProvider {
|
||||
// redirectUri = redirectUri.replace("localhost:3008", "certd.handfree.work")
|
||||
// }
|
||||
const res = await this.ctx.http.request({
|
||||
url: `${this.endpoint}/connect.php?act=login&appid=${this.appId}&appkey=${this.appKey}&type=${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)
|
||||
|
||||
return {
|
||||
loginUrl: res.url,
|
||||
ticketValue: {
|
||||
state: "",
|
||||
},
|
||||
ticketValue: {},
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -79,19 +79,13 @@ gitee.userInfo = https://gitee.com/api/v5/user
|
||||
async buildLoginUrl(params: BuildLoginUrlReq) {
|
||||
|
||||
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 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
|
||||
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 {
|
||||
loginUrl,
|
||||
ticketValue: {
|
||||
state,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -30,19 +30,12 @@ export class GithubOauthProvider extends BaseAddon implements IOauthProvider {
|
||||
async buildLoginUrl(params: BuildLoginUrlReq) {
|
||||
|
||||
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 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 {
|
||||
loginUrl,
|
||||
ticketValue: {
|
||||
state,
|
||||
},
|
||||
ticketValue: { },
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -30,18 +30,13 @@ export class GoogleOauthProvider extends BaseAddon implements IOauthProvider {
|
||||
async buildLoginUrl(params: BuildLoginUrlReq) {
|
||||
|
||||
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 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 {
|
||||
loginUrl,
|
||||
ticketValue: {
|
||||
state,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -42,18 +42,12 @@ export class MicrosoftOauthProvider extends BaseAddon implements IOauthProvider
|
||||
async buildLoginUrl(params: BuildLoginUrlReq) {
|
||||
|
||||
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 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 {
|
||||
loginUrl,
|
||||
ticketValue: {
|
||||
state,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user