diff --git a/docs/.vitepress/config.ts b/docs/.vitepress/config.ts index faa6b780..e32d1353 100644 --- a/docs/.vitepress/config.ts +++ b/docs/.vitepress/config.ts @@ -1,158 +1,166 @@ -import { defineConfig } from "vitepress"; +import {defineConfig} from "vitepress"; // Import lightbox plugin import lightbox from "vitepress-plugin-lightbox"; // https://vitepress.dev/reference/site-config export default defineConfig({ - title: "Certd", - titleTemplate: "开源SSL证书管理工具,证书自动化申请部署,让你的网站证书永不过期", - description: "Certd帮助文档,Certd是一款开源免费的全自动SSL证书管理工具;证书自动化申请部署流水线;自动证书申请、更新、续期;通配符证书,泛域名证书申请;证书自动化部署到阿里云、腾讯云、主机、群晖、宝塔。", - markdown: { - config: (md) => { - // Use lightbox plugin - md.use(lightbox, {}); - } - }, - sitemap: { - hostname: 'https://certd.docmirror.cn' - }, - head: [ - // [ - // 'meta', - // { - // name: 'viewport', - // content: - // 'width=device-width,initial-scale=1,minimfast-cum-scale=1.0,maximum-scale=1.0,user-scalable=no', - // }, - // ], - ["meta", { - name: "keywords", - content: "证书自动申请、证书自动更新、证书自动续期、证书自动续签、证书管理工具、Certd、SSL证书自动部署、证书自动化,https证书,pfx证书,der证书,TLS证书,nginx证书自动续签自动部署,SSL平台,证书管理平台,证书流水线" - }], - // ["meta", { name: "google-site-verification",content: "V5XLTSnXoT15uQotwpxJoQolUo2d5UbSL-TacsyOsC0"}], - // - // ["meta", {name: "baidu-site-verification",content: "codeva-MiWN8Y07Ua"}], - ["link", { rel: "icon", href: "/static/logo/logo.svg" }] - ], - themeConfig: { - logo: "/static/logo/logo.svg", - search: { - provider: "local", - options: { - detailedView: true, - translations: { - button: { - buttonText: "搜索文档", - buttonAriaLabel: "搜索文档" - }, - modal: { - noResultsText: "无法找到相关结果", - resetButtonTitle: "清除查询条件", - footer: { - selectText: "选择", - closeText: "关闭", - navigateText: "切换" + title: "Certd", + titleTemplate: "开源SSL证书管理工具,证书自动化申请部署,让你的网站证书永不过期", + description: "Certd帮助文档,Certd是一款开源免费的全自动SSL证书管理工具;证书自动化申请部署流水线;自动证书申请、更新、续期;通配符证书,泛域名证书申请;证书自动化部署到阿里云、腾讯云、主机、群晖、宝塔。", + markdown: { + config: (md) => { + // Use lightbox plugin + md.use(lightbox, {}); + } + }, + sitemap: { + hostname: 'https://certd.docmirror.cn' + }, + head: [ + // [ + // 'meta', + // { + // name: 'viewport', + // content: + // 'width=device-width,initial-scale=1,minimfast-cum-scale=1.0,maximum-scale=1.0,user-scalable=no', + // }, + // ], + ["meta", { + name: "keywords", + content: "证书自动申请、证书自动更新、证书自动续期、证书自动续签、证书管理工具、Certd、SSL证书自动部署、证书自动化,https证书,pfx证书,der证书,TLS证书,nginx证书自动续签自动部署,SSL平台,证书管理平台,证书流水线" + }], + // ["meta", { name: "google-site-verification",content: "V5XLTSnXoT15uQotwpxJoQolUo2d5UbSL-TacsyOsC0"}], + // + // ["meta", {name: "baidu-site-verification",content: "codeva-MiWN8Y07Ua"}], + ["link", {rel: "icon", href: "/static/logo/logo.svg"}] + ], + themeConfig: { + logo: "/static/logo/logo.svg", + search: { + provider: "local", + options: { + detailedView: true, + translations: { + button: { + buttonText: "搜索文档", + buttonAriaLabel: "搜索文档" + }, + modal: { + noResultsText: "无法找到相关结果", + resetButtonTitle: "清除查询条件", + footer: { + selectText: "选择", + closeText: "关闭", + navigateText: "切换" + } + } + } } - } - } - } - }, - // https://vitepress.dev/reference/default-theme-config - nav: [ - { text: "首页", link: "/" }, - { text: "指南", link: "/guide/" }, - { text: "插件", link: "/deploy/" }, - { text: "商业版", link: "/comm/" }, - { text: "Demo体验", link: "https://certd.handfree.work" } - ], - sidebar: { - "/guide/": [ - { - text: "入门", - items: [ - { text: "简介", link: "/guide/" }, - { text: "快速开始", link: "/guide/start.md" }, - { - text: "私有化部署", - items: [ - { text: "docker部署", link: "/guide/install/docker/" }, - { text: "宝塔面板部署", link: "/guide/install/baota/" }, - { text: "1Panel部署", link: "/guide/install/1panel/" }, - { text: "群晖部署", link: "/guide/use/synology/" }, - { text: "源码部署", link: "/guide/install/source/" } - ] - }, - { text: "演示教程", link: "/guide/tutorial.md" }, - { text: "版本升级", link: "/guide/install/upgrade.md" } - ] }, - { - text: "特性", - items: [ - { text: "CNAME代理校验", link: "/guide/feature/cname/index.md" }, - { text: "插件列表", link: "/guide/plugins.md" }, - { text: "多数据库支持", link: "/guide/install/database.md" }, - { text: "开放接口", link: "/guide/open/index.md" } - ] - }, - { - text: "常见问题", - items: [ - { text: "群晖证书部署", link: "/guide/use/synology/" }, - { text: "腾讯云密钥获取", link: "/guide/use/tencent/" }, - { text: "连接windows主机", link: "/guide/use/host/windows.md" }, - { text: "Google EAB获取", link: "/guide/use/google/" }, - { text: "阿里云相关", link: "/guide/use/aliyun/" }, - { text: "忘记密码", link: "/guide/use/forgotpasswd/" }, - { text: "数据备份", link: "/guide/use/backup/" }, - { text: "Certd本身的证书更新", link: "/guide/use/https/index.md" }, - { text: "js脚本插件使用", link: "/guide/use/custom-script/index.md" }, - { text: "邮箱配置", link: "/guide/use/email/index.md" }, - { text: "IPv6支持", link: "/guide/use/setting/ipv6.md" }, - ] - }, - { - text: "其他", - items: [ - { text: "贡献代码", link: "/guide/development/index.md" }, - { text: "更新日志", link: "/guide/changelogs/CHANGELOG.md" }, - { text: "镜像说明", link: "/guide/image.md" }, - { text: "联系我们", link: "/guide/contact/" }, - { text: "捐赠", link: "/guide/donate/" }, - { text: "开源协议", link: "/guide/license/" }, - { text: "我的其他开源项目", link: "/guide/link/" }, + // https://vitepress.dev/reference/default-theme-config + nav: [ + {text: "首页", link: "/"}, + {text: "指南", link: "/guide/"}, + {text: "Demo体验", link: "https://certd.handfree.work"} + ], + sidebar: { + "/guide/": [ + { + text: "入门", + items: [ + {text: "简介", link: "/guide/"}, + {text: "快速开始", link: "/guide/start.md"}, + { + text: "私有化部署", + items: [ + {text: "docker部署", link: "/guide/install/docker/"}, + {text: "宝塔面板部署", link: "/guide/install/baota/"}, + {text: "1Panel部署", link: "/guide/install/1panel/"}, + {text: "群晖部署", link: "/guide/use/synology/"}, + {text: "源码部署", link: "/guide/install/source/"} + ] + }, + {text: "演示教程", link: "/guide/tutorial.md"}, + {text: "版本升级", link: "/guide/install/upgrade.md"} + ] + }, + { + text: "特性", + items: [ + {text: "CNAME代理校验", link: "/guide/feature/cname/index.md"}, + {text: "插件列表", link: "/guide/plugins.md"}, + {text: "多数据库支持", link: "/guide/install/database.md"}, + {text: "开放接口", link: "/guide/open/index.md"}, + { + text: "站点安全", items: [ + {text: "安全特性", link: "/guide/feature/safe"}, + {text: "站点隐藏", link: "/guide/feature/safe/hidden"}, + {text: "安全生产建议", link: "/guide/feature/safe/suggest"}, + ] + }, - ] - } - ], - "/deploy/":[ - { - text: "部署证书插件", - items: [ - { text: "插件说明", link: "/deploy/index.md" }, - { text: "部署到ESXi", link: "/deploy/ESXi/index.md" }, - ] - } - ], - "/comm/": [ - { - text: "商业版", - items: [ - { text: "支付宝配置", link: "/comm/payments/alipay.md" }, - { text: "微信支付配置", link: "/comm/payments/wxpay.md" }, - { text: "彩虹易支付配置", link: "/comm/payments/yizhifu.md" }, - ] - } - ] - , - }, + ] + }, + { + text: "常见问题", + items: [ + {text: "群晖证书部署", link: "/guide/use/synology/"}, + {text: "腾讯云密钥获取", link: "/guide/use/tencent/"}, + {text: "连接windows主机", link: "/guide/use/host/windows.md"}, + {text: "Google EAB获取", link: "/guide/use/google/"}, + {text: "阿里云相关", link: "/guide/use/aliyun/"}, + {text: "忘记密码", link: "/guide/use/forgotpasswd/"}, + {text: "数据备份", link: "/guide/use/backup/"}, + {text: "Certd本身的证书更新", link: "/guide/use/https/index.md"}, + {text: "js脚本插件使用", link: "/guide/use/custom-script/index.md"}, + {text: "邮箱配置", link: "/guide/use/email/index.md"}, + {text: "IPv6支持", link: "/guide/use/setting/ipv6.md"}, + {text: "其他插件使用", link: "/deploy/"}, + {text: "商业版说明", link: "/comm/"}, + ] + }, + { + text: "其他", + items: [ + {text: "贡献代码", link: "/guide/development/index.md"}, + {text: "更新日志", link: "/guide/changelogs/CHANGELOG.md"}, + {text: "镜像说明", link: "/guide/image.md"}, + {text: "联系我们", link: "/guide/contact/"}, + {text: "捐赠", link: "/guide/donate/"}, + {text: "开源协议", link: "/guide/license/"}, + {text: "我的其他开源项目", link: "/guide/link/"}, - socialLinks: [ - { icon: "github", link: "https://github.com/certd/certd" } - ], - footer: { - message: "Certd帮助文档 | 粤ICP备14088435号 ", - copyright: "Copyright © 2021-present handfree.work " + ] + } + ], + "/deploy/": [ + { + text: "部署证书插件", + items: [ + {text: "插件说明", link: "/deploy/index.md"}, + {text: "部署到ESXi", link: "/deploy/ESXi/index.md"}, + ] + } + ], + "/comm/": [ + { + text: "商业版", + items: [ + {text: "支付宝配置", link: "/comm/payments/alipay.md"}, + {text: "微信支付配置", link: "/comm/payments/wxpay.md"}, + {text: "彩虹易支付配置", link: "/comm/payments/yizhifu.md"}, + ] + } + ] + , + }, + + socialLinks: [ + {icon: "github", link: "https://github.com/certd/certd"} + ], + footer: { + message: "Certd帮助文档 | 粤ICP备14088435号 ", + copyright: "Copyright © 2021-present handfree.work " + } } - } }); diff --git a/docs/guide/feature/safe/hidden/images/hidden1.png b/docs/guide/feature/safe/hidden/images/hidden1.png new file mode 100644 index 00000000..1bbec32c Binary files /dev/null and b/docs/guide/feature/safe/hidden/images/hidden1.png differ diff --git a/docs/guide/feature/safe/hidden/images/hidden2.png b/docs/guide/feature/safe/hidden/images/hidden2.png new file mode 100644 index 00000000..baf9f11f Binary files /dev/null and b/docs/guide/feature/safe/hidden/images/hidden2.png differ diff --git a/docs/guide/feature/safe/hidden/index.md b/docs/guide/feature/safe/hidden/index.md new file mode 100644 index 00000000..d56e80c9 --- /dev/null +++ b/docs/guide/feature/safe/hidden/index.md @@ -0,0 +1,25 @@ +# 站点隐藏 + +* 一般来说Certd设置好之后,很少需要访问。 +* 所以我们`平时`可以把`站点访问关闭`,需要的时候再打开,减少站点被攻击的风险 + +## 1、开启站点隐藏 +`系统管理->系统设置->安全设置->站点隐藏 ` + + +![](./images/hidden1.png) + +:::warning + +注意保存好`解除地址`和`解除密码` + +::: + +## 2、临时关闭站点隐藏 + +访问上面的`解除地址`,输入`解除密码`,`临时解除`站点隐藏 + +![](./images/hidden2.png) + +## 3、忘记解除地址和解除密码怎么办 +登录服务器,在数据库平级的目录下创建`.unhidden`文件即可`临时解除`站点隐藏 diff --git a/docs/guide/feature/safe/images/access.png b/docs/guide/feature/safe/images/access.png new file mode 100644 index 00000000..f483330a Binary files /dev/null and b/docs/guide/feature/safe/images/access.png differ diff --git a/docs/guide/feature/safe/images/hidden.png b/docs/guide/feature/safe/images/hidden.png new file mode 100644 index 00000000..b577d574 Binary files /dev/null and b/docs/guide/feature/safe/images/hidden.png differ diff --git a/docs/guide/feature/safe/images/login.png b/docs/guide/feature/safe/images/login.png new file mode 100644 index 00000000..4688a702 Binary files /dev/null and b/docs/guide/feature/safe/images/login.png differ diff --git a/docs/guide/feature/safe/index.md b/docs/guide/feature/safe/index.md new file mode 100644 index 00000000..f06dc18a --- /dev/null +++ b/docs/guide/feature/safe/index.md @@ -0,0 +1,34 @@ +# 站点安全特性 + +Certd 存储了证书以及授权等敏感数据,所以需要严格保障安全。 +我们非常重视您的数据安全,提供了以下安全特性 + +## 1、 授权数据加密存储【默认开启】 +* 所有的授权敏感字段会加密后存储 +* 每个用户独立维护授权数据,连管理员都无权查看 + +![星号部分为加密数据](./images/access.png) +星号部分为加密数据 + +## 2、 密码防爆破【默认开启】 +* 登录失败次数过多,账号将被锁定,最高24小时(重启服务可解除锁定) +* 用户登录密码加密hash后存储,无法计算出密码明文 + ![](./images/login.png) + +## 3、站点隐藏【建议开启】 +* 一般来说Certd设置好之后,后续很少需要访问修改。 +* 所以我们平时可以把站点访问关闭,需要的时候再打开,减少站点被攻击的风险 +* 请前往 `系统管理->系统设置->安全设置->开启站点隐藏` +* [站点隐藏设置说明](./hidden/) + ![](./images/hidden.png) + +## 4、登录二次验证 + +待实现 + +## 5、数据库自动备份【建议开启】 +* [自动备份设置说明](../../use/backup/) + + +## 更多安全生产建议 +[安全生产建议](./suggest.md) \ No newline at end of file diff --git a/docs/guide/feature/safe/suggest.md b/docs/guide/feature/safe/suggest.md new file mode 100644 index 00000000..87f37fc4 --- /dev/null +++ b/docs/guide/feature/safe/suggest.md @@ -0,0 +1,10 @@ +# 安全生产建议 + +尽管`Cert`本身实现了很多安全特性,但`外部环境的安全`仍需要您来确保。 +请`务必`遵循如下建议做好安全防护 + +* 请`务必`使用`HTTPS协议`访问本应用,避免被中间人攻击 +* 请`务必`使用`web应用防火墙`防护本应用,防止XSS、SQL注入等攻击 +* 请`务必`做好`服务器本身`的安全防护,防止数据库泄露 +* 请`务必`做好[`数据备份`](../../use/backup/),避免数据丢失 +* 建议开启[`站点隐藏`](./hidden/)功能 diff --git a/packages/core/basic/src/utils/util.hash.ts b/packages/core/basic/src/utils/util.hash.ts index 0557c324..87f6a0e8 100644 --- a/packages/core/basic/src/utils/util.hash.ts +++ b/packages/core/basic/src/utils/util.hash.ts @@ -1,18 +1,18 @@ -import crypto, { BinaryToTextEncoding } from 'crypto'; +import crypto, { BinaryToTextEncoding } from "crypto"; -function md5(data: string, digest: BinaryToTextEncoding = 'hex') { - return crypto.createHash('md5').update(data).digest(digest); +function md5(data: string, digest: BinaryToTextEncoding = "hex") { + return crypto.createHash("md5").update(data).digest(digest); } -function sha256(data: string, digest: BinaryToTextEncoding = 'hex') { - return crypto.createHash('sha256').update(data).digest(digest); +function sha256(data: string, digest: BinaryToTextEncoding = "hex") { + return crypto.createHash("sha256").update(data).digest(digest); } -function hmacSha256(data: string, digest: BinaryToTextEncoding = 'base64') { - return crypto.createHmac('sha256', data).update(Buffer.alloc(0)).digest(digest); +function hmacSha256(data: string, digest: BinaryToTextEncoding = "base64") { + return crypto.createHmac("sha256", data).update(Buffer.alloc(0)).digest(digest); } function base64(data: string) { - return Buffer.from(data).toString('base64'); + return Buffer.from(data).toString("base64"); } export const hashUtils = { md5, diff --git a/packages/libs/lib-server/src/basic/constants.ts b/packages/libs/lib-server/src/basic/constants.ts index 74aa2ac9..bfdaf507 100644 --- a/packages/libs/lib-server/src/basic/constants.ts +++ b/packages/libs/lib-server/src/basic/constants.ts @@ -66,10 +66,15 @@ export const Constants = { code: 404, message: '页面/文件/资源不存在', }, + preview: { code: 10001, message: '对不起,预览环境不允许修改此数据', }, + siteOff:{ + code: 10010, + message: '站点已关闭', + }, openKeyError: { code: 20000, message: 'ApiToken错误', diff --git a/packages/libs/lib-server/src/basic/exception/index.ts b/packages/libs/lib-server/src/basic/exception/index.ts index da4c38b2..14b767b7 100644 --- a/packages/libs/lib-server/src/basic/exception/index.ts +++ b/packages/libs/lib-server/src/basic/exception/index.ts @@ -7,3 +7,4 @@ export * from './vip-exception.js'; export * from './common-exception.js'; export * from './not-found-exception.js'; export * from './param-exception.js'; +export * from './site-off-exception.js'; diff --git a/packages/libs/lib-server/src/basic/exception/site-off-exception.ts b/packages/libs/lib-server/src/basic/exception/site-off-exception.ts new file mode 100644 index 00000000..f2527e16 --- /dev/null +++ b/packages/libs/lib-server/src/basic/exception/site-off-exception.ts @@ -0,0 +1,9 @@ +import { Constants } from '../constants.js'; +import { BaseException } from './base-exception.js'; +/** + */ +export class SiteOffException extends BaseException { + constructor(message) { + super('SiteOffException', Constants.res.siteOff.code, message ? message : Constants.res.siteOff.message); + } +} diff --git a/packages/libs/lib-server/src/system/settings/service/models.ts b/packages/libs/lib-server/src/system/settings/service/models.ts index 1d53ac8b..61161f7a 100644 --- a/packages/libs/lib-server/src/system/settings/service/models.ts +++ b/packages/libs/lib-server/src/system/settings/service/models.ts @@ -171,7 +171,7 @@ export class SysSuiteSetting extends BaseSettings { static __key__ = 'sys.suite'; static __access__ = 'private'; - enabled = false; + enabled:boolean = false; registerGift?: { productId: number; @@ -180,3 +180,25 @@ export class SysSuiteSetting extends BaseSettings { intro?: string; } + + +export type SiteHidden = { + enabled: boolean; + openPath?: string; + //md5 hash 两次后保存 + openPassword?: string; + autoHiddenTimes?: number; + hiddenOpenApi?: boolean +}; +export class SysSafeSetting extends BaseSettings { + static __title__ = '站点安全设置'; + static __key__ = 'sys.safe'; + static __access__ = 'private'; + + // 站点隐藏 + hidden:SiteHidden = { + enabled: false, + hiddenOpenApi:false, + autoHiddenTimes: 5, + }; +} diff --git a/packages/ui/certd-client/package.json b/packages/ui/certd-client/package.json index c15939f0..c311ffd6 100644 --- a/packages/ui/certd-client/package.json +++ b/packages/ui/certd-client/package.json @@ -84,6 +84,7 @@ "qrcode": "^1.5.4", "radix-vue": "^1.9.16", "sortablejs": "^1.15.3", + "spark-md5": "^3.0.2", "tailwind-merge": "^3.0.2", "tailwindcss-animate": "^1.0.7", "theme-colors": "^0.1.0", diff --git a/packages/ui/certd-client/src/components/tutorial/tutorial-steps.vue b/packages/ui/certd-client/src/components/tutorial/tutorial-steps.vue index b6322ec2..e20e0dab 100644 --- a/packages/ui/certd-client/src/components/tutorial/tutorial-steps.vue +++ b/packages/ui/certd-client/src/components/tutorial/tutorial-steps.vue @@ -54,23 +54,23 @@ const steps = ref([ descriptions: ["本教程演示如何自动申请证书并部署到Nginx上", "仅需3步,全自动申请部署证书"], body: () => { return ; - } + }, }, { image: "/static/doc/images/1-add.png", title: "创建证书流水线", - descriptions: ["点击添加证书流水线,填写证书申请信息"] + descriptions: ["点击添加证书流水线,填写证书申请信息"], }, { image: "/static/doc/images/3-add-success.png", title: "流水线创建成功", - descriptions: ["点击手动触发即可申请证书"] + descriptions: ["点击手动触发即可申请证书"], }, { title: "接下来演示如何自动部署证书", - descriptions: ["如果您只需要申请证书,那么到这一步就可以了"] - } - ] + descriptions: ["如果您只需要申请证书,那么到这一步就可以了"], + }, + ], }, { title: "添加部署证书任务", @@ -79,29 +79,29 @@ const steps = ref([ { image: "/static/doc/images/5-1-add-host.png", title: "添加证书部署任务", - descriptions: ["这里演示自动部署证书到nginx", "本系统提供海量部署插件,满足您的各种部署需求"] + descriptions: ["这里演示自动部署证书到nginx", "本系统提供海量部署插件,满足您的各种部署需求"], }, { image: "/static/doc/images/5-2-add-host.png", title: "填写任务参数", - descriptions: ["填写主机上证书文件的路径", "选择主机ssh登录授权"] + descriptions: ["填写主机上证书文件的路径", "选择主机ssh登录授权"], }, { image: "/static/doc/images/5-3-add-host.png", title: "让新证书生效", - descriptions: ["执行重启脚本", "让证书生效"] + descriptions: ["执行重启脚本", "让证书生效"], }, { image: "/static/doc/images/5-4-add-host.png", title: "部署任务添加成功", - descriptions: ["现在可以运行"] + descriptions: ["现在可以运行"], }, { image: "/static/doc/images/5-5-plugin-list.png", title: "本系统提供茫茫多的部署插件", - descriptions: ["您可以根据自身需求将证书部署到各种应用和平台"] - } - ] + descriptions: ["您可以根据自身需求将证书部署到各种应用和平台"], + }, + ], }, { title: "运行与测试", @@ -110,44 +110,44 @@ const steps = ref([ { image: "/static/doc/images/9-start.png", title: "运行测试一下", - descriptions: ["点击手动触发按钮,即可测试运行"] + descriptions: ["点击手动触发按钮,即可测试运行"], }, { image: "/static/doc/images/10-1-log.png", title: "查看日志", - descriptions: ["点击任务可以查看状态和日志"] + descriptions: ["点击任务可以查看状态和日志"], }, { image: "/static/doc/images/11-1-error.png", title: "执行失败如何排查", - descriptions: ["查看错误日志"] + descriptions: ["查看错误日志"], }, { image: "/static/doc/images/11-2-error.png", title: "执行失败如何排查", - descriptions: ["查看错误日志", "这里报的是nginx容器不存在,修改命令,改成正确的nginx容器名称即可"] + descriptions: ["查看错误日志", "这里报的是nginx容器不存在,修改命令,改成正确的nginx容器名称即可"], }, { image: "/static/doc/images/12-1-log-success.png", title: "执行成功", - descriptions: ["修改正确后,重新点击手动触发,重新运行一次,执行成功"] + descriptions: ["修改正确后,重新点击手动触发,重新运行一次,执行成功"], }, { image: "/static/doc/images/12-2-skip-log.png", title: "成功后自动跳过", - descriptions: ["可以看到成功过的将会自动跳过,不会重复执行,只有当参数变更或者证书更新了,才会重新运行"] + descriptions: ["可以看到成功过的将会自动跳过,不会重复执行,只有当参数变更或者证书更新了,才会重新运行"], }, { image: "/static/doc/images/13-1-result.png", title: "查看证书部署成功", - descriptions: ["访问nginx上的网站,可以看到证书已经部署成功"] + descriptions: ["访问nginx上的网站,可以看到证书已经部署成功"], }, { image: "/static/doc/images/13-3-download.png", title: "还可以下载证书,手动部署", - descriptions: ["如果还没有好用的部署插件,没办法自动部署,你还可以下载证书,手动部署"] - } - ] + descriptions: ["如果还没有好用的部署插件,没办法自动部署,你还可以下载证书,手动部署"], + }, + ], }, { title: "设置定时执行和邮件通知", @@ -156,22 +156,19 @@ const steps = ref([ { image: "/static/doc/images/14-timer.png", title: "设置定时执行", - descriptions: [ - "流水线测试成功,接下来配置定时触发,以后每天定时执行就不用管了", - "推荐配置每天运行一次,在到期前35天才会重新申请新证书并部署,没到期前会自动跳过,不会重复申请。" - ] + descriptions: ["流水线测试成功,接下来配置定时触发,以后每天定时执行就不用管了", "推荐配置每天运行一次,在到期前35天才会重新申请新证书并部署,没到期前会自动跳过,不会重复申请。"], }, { image: "/static/doc/images/15-1-email.png", title: "设置邮件通知", - descriptions: ["建议选择监听'错误时'和'错误转成功'两种即可,在意外失败时可以尽快去排查问题,(基础版需要配置邮件服务器)"] + descriptions: ["建议选择监听'错误时'和'错误转成功'两种即可,在意外失败时可以尽快去排查问题,(基础版需要配置邮件服务器)"], }, { title: "教程结束", - descriptions: ["感谢观看,希望对你有所帮助"] - } - ] - } + descriptions: ["感谢观看,希望对你有所帮助"], + }, + ], + }, ]); const current = ref(0); diff --git a/packages/ui/certd-client/src/utils/index.ts b/packages/ui/certd-client/src/utils/index.ts index 47a9457c..5e6c0d85 100644 --- a/packages/ui/certd-client/src/utils/index.ts +++ b/packages/ui/certd-client/src/utils/index.ts @@ -16,6 +16,6 @@ export const util = { router: routerUtils, tree: treeUtils, hash: hashUtils, - amount: amountUtils + amount: amountUtils, }; export const utils = util; diff --git a/packages/ui/certd-client/src/utils/util.amount.ts b/packages/ui/certd-client/src/utils/util.amount.ts index 3d6703e8..f457ba42 100644 --- a/packages/ui/certd-client/src/utils/util.amount.ts +++ b/packages/ui/certd-client/src/utils/util.amount.ts @@ -5,5 +5,5 @@ export const amountUtils = { toYuan(amount: number): number { return parseFloat((amount / 100).toFixed(2)); - } + }, }; diff --git a/packages/ui/certd-client/src/utils/util.common.ts b/packages/ui/certd-client/src/utils/util.common.ts index afa2f7a3..53a57894 100644 --- a/packages/ui/certd-client/src/utils/util.common.ts +++ b/packages/ui/certd-client/src/utils/util.common.ts @@ -47,4 +47,13 @@ export default { } return desc.replace(/\[(.*)\]\((.*)\)/g, '$1'); }, + + randomString(length: number) { + const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; + let result = ""; + for (let i = 0; i < length; i++) { + result += chars.charAt(Math.floor(Math.random() * chars.length)); + } + return result; + }, }; diff --git a/packages/ui/certd-client/src/utils/util.hash.ts b/packages/ui/certd-client/src/utils/util.hash.ts index 38a8abf3..66d7b58c 100644 --- a/packages/ui/certd-client/src/utils/util.hash.ts +++ b/packages/ui/certd-client/src/utils/util.hash.ts @@ -1,5 +1,5 @@ export const hashUtils = { md5(data: string) { throw new Error("Not implemented"); - } + }, }; diff --git a/packages/ui/certd-client/src/utils/util.site.ts b/packages/ui/certd-client/src/utils/util.site.ts index 8c1b3f5d..9aeda613 100644 --- a/packages/ui/certd-client/src/utils/util.site.ts +++ b/packages/ui/certd-client/src/utils/util.site.ts @@ -7,5 +7,5 @@ export const site = { title: function (titleText: string, baseTitle?: string) { const processTitle = baseTitle || env.TITLE || "Certd"; window.document.title = `${processTitle}${titleText ? ` | ${titleText}` : ""}`; - } + }, }; diff --git a/packages/ui/certd-client/src/utils/util.storage.ts b/packages/ui/certd-client/src/utils/util.storage.ts index bbcfe496..70343ad0 100644 --- a/packages/ui/certd-client/src/utils/util.storage.ts +++ b/packages/ui/certd-client/src/utils/util.storage.ts @@ -46,7 +46,7 @@ export class WebStorage { const stringData = JSON.stringify({ value, time: Date.now(), - expire: expire != null ? new Date().getTime() + expire * 1000 : null + expire: expire != null ? new Date().getTime() + expire * 1000 : null, }); this.storage.setItem(this.getKey(key), stringData); } diff --git a/packages/ui/certd-client/src/views/sys/authority/user/api.ts b/packages/ui/certd-client/src/views/sys/authority/user/api.ts index 96c3fd9d..b904a561 100644 --- a/packages/ui/certd-client/src/views/sys/authority/user/api.ts +++ b/packages/ui/certd-client/src/views/sys/authority/user/api.ts @@ -4,7 +4,7 @@ export async function GetList(query: any) { return await request({ url: apiPrefix + "/page", method: "post", - data: query + data: query, }); } @@ -12,7 +12,7 @@ export async function AddObj(obj: any) { return await request({ url: apiPrefix + "/add", method: "post", - data: obj + data: obj, }); } @@ -20,7 +20,7 @@ export async function UpdateObj(obj: any) { return await request({ url: apiPrefix + "/update", method: "post", - data: obj + data: obj, }); } @@ -28,7 +28,7 @@ export async function DelObj(id: any) { return await request({ url: apiPrefix + "/delete", method: "post", - params: { id } + params: { id }, }); } @@ -36,6 +36,14 @@ export async function GetObj(id: any) { return await request({ url: apiPrefix + "/info", method: "post", - params: { id } + params: { id }, + }); +} + +export async function Unlock(id: any) { + return await request({ + url: apiPrefix + "/unlockBlock", + method: "post", + data: { id }, }); } diff --git a/packages/ui/certd-client/src/views/sys/authority/user/crud.tsx b/packages/ui/certd-client/src/views/sys/authority/user/crud.tsx index f12c05df..5fe4af49 100644 --- a/packages/ui/certd-client/src/views/sys/authority/user/crud.tsx +++ b/packages/ui/certd-client/src/views/sys/authority/user/crud.tsx @@ -1,6 +1,7 @@ import * as api from "./api"; import { AddReq, CreateCrudOptionsProps, CreateCrudOptionsRet, DelReq, dict, EditReq, UserPageQuery, UserPageRes } from "@fast-crud/fast-crud"; import { useUserStore } from "/@/store/user"; +import { Modal, notification } from "ant-design-vue"; export default function ({ crudExpose }: CreateCrudOptionsProps): CreateCrudOptionsRet { const pageRequest = async (query: UserPageQuery): Promise => { @@ -26,16 +27,36 @@ export default function ({ crudExpose }: CreateCrudOptionsProps): CreateCrudOpti pageRequest, addRequest, editRequest, - delRequest + delRequest, }, rowHandle: { - fixed: "right" + fixed: "right", + buttons: { + unlock: { + title: "解除登录锁定", + text: null, + type: "link", + icon: "ion:lock-open-outline", + click: async ({ row }) => { + Modal.confirm({ + title: "提示", + content: "确定要解除该用户的登录锁定吗?", + onOk: async () => { + await api.Unlock(row.id); + notification.success({ + message: "解除成功", + }); + }, + }); + }, + }, + }, }, table: { scroll: { //使用固定列时需要设置此值,并且大于等于列宽度之和的值 - x: 1400 - } + x: 1400, + }, }, columns: { id: { @@ -44,8 +65,8 @@ export default function ({ crudExpose }: CreateCrudOptionsProps): CreateCrudOpti form: { show: false }, // 表单配置 column: { width: 100, - sorter: true - } + sorter: true, + }, }, createTime: { title: "创建时间", @@ -53,8 +74,8 @@ export default function ({ crudExpose }: CreateCrudOptionsProps): CreateCrudOpti form: { show: false }, // 表单配置 column: { width: 180, - sorter: true - } + sorter: true, + }, }, // updateTime: { // title: "修改时间", @@ -72,64 +93,64 @@ export default function ({ crudExpose }: CreateCrudOptionsProps): CreateCrudOpti form: { rules: [ { required: true, message: "请输入用户名" }, - { max: 50, message: "最大50个字符" } - ] + { max: 50, message: "最大50个字符" }, + ], }, editForm: { component: { disabled: false } }, column: { sorter: true, - width: 200 - } + width: 200, + }, }, password: { title: "密码", type: "text", key: "password", column: { - show: false + show: false, }, form: { rules: [{ max: 50, message: "最大50个字符" }], component: { - showPassword: true + showPassword: true, }, - helper: "填写则修改密码" - } + helper: "填写则修改密码", + }, }, nickName: { title: "昵称", type: "text", search: { show: true }, // 开启查询 form: { - rules: [{ max: 50, message: "最大50个字符" }] + rules: [{ max: 50, message: "最大50个字符" }], }, column: { - sorter: true - } + sorter: true, + }, }, email: { title: "邮箱", type: "text", search: { show: true }, // 开启查询 form: { - rules: [{ max: 50, message: "最大50个字符" }] + rules: [{ max: 50, message: "最大50个字符" }], }, column: { sorter: true, - width: 160 - } + width: 160, + }, }, mobile: { title: "手机号", type: "text", search: { show: true }, // 开启查询 form: { - rules: [{ max: 50, message: "最大50个字符" }] + rules: [{ max: 50, message: "最大50个字符" }], }, column: { sorter: true, - width: 130 - } + width: 130, + }, }, avatar: { title: "头像", @@ -140,12 +161,12 @@ export default function ({ crudExpose }: CreateCrudOptionsProps): CreateCrudOpti //设置高度,修复操作列错位的问题 style: { height: "30px", - width: "auto" + width: "auto", }, buildUrl(key: string) { return `/api/basic/file/download?&key=` + key; - } - } + }, + }, }, form: { component: { @@ -154,7 +175,7 @@ export default function ({ crudExpose }: CreateCrudOptionsProps): CreateCrudOpti cropper: { aspectRatio: 1, autoCropArea: 1, - viewMode: 0 + viewMode: 0, }, onReady: null, uploader: { @@ -162,17 +183,17 @@ export default function ({ crudExpose }: CreateCrudOptionsProps): CreateCrudOpti action: "/basic/file/upload", name: "file", headers: { - Authorization: "Bearer " + userStore.getToken + Authorization: "Bearer " + userStore.getToken, }, successHandle(res: any) { return res; - } + }, }, buildUrl(key: string) { return `/api/basic/file/download?&key=` + key; - } - } - } + }, + }, + }, }, status: { title: "状态", @@ -180,24 +201,24 @@ export default function ({ crudExpose }: CreateCrudOptionsProps): CreateCrudOpti dict: dict({ data: [ { label: "启用", value: 1, color: "green" }, - { label: "禁用", value: 0, color: "red" } - ] + { label: "禁用", value: 0, color: "red" }, + ], }), column: { align: "center", sorter: true, - width: 100 - } + width: 100, + }, }, remark: { title: "备注", type: "text", column: { - sorter: true + sorter: true, }, form: { - rules: [{ max: 100, message: "最大100个字符" }] - } + rules: [{ max: 100, message: "最大100个字符" }], + }, }, roles: { title: "角色", @@ -205,17 +226,17 @@ export default function ({ crudExpose }: CreateCrudOptionsProps): CreateCrudOpti dict: dict({ url: "/sys/authority/role/list", value: "id", - label: "name" + label: "name", }), // 数据字典 form: { - component: { mode: "multiple" } + component: { mode: "multiple" }, }, column: { width: 250, - sortable: true - } - } - } - } + sortable: true, + }, + }, + }, + }, }; } diff --git a/packages/ui/certd-client/src/views/sys/settings/api.ts b/packages/ui/certd-client/src/views/sys/settings/api.ts index e2d57609..cb0c3299 100644 --- a/packages/ui/certd-client/src/views/sys/settings/api.ts +++ b/packages/ui/certd-client/src/views/sys/settings/api.ts @@ -103,3 +103,4 @@ export async function GetSmsTypeDefine(type: string) { }, }); } + diff --git a/packages/ui/certd-client/src/views/sys/settings/index.vue b/packages/ui/certd-client/src/views/sys/settings/index.vue index a2624b6a..c16a76c5 100644 --- a/packages/ui/certd-client/src/views/sys/settings/index.vue +++ b/packages/ui/certd-client/src/views/sys/settings/index.vue @@ -5,8 +5,8 @@
- - + + @@ -14,6 +14,9 @@ + + +
@@ -23,6 +26,7 @@ import SettingBase from "/@/views/sys/settings/tabs/base.vue"; import SettingRegister from "/@/views/sys/settings/tabs/register.vue"; import SettingPayment from "/@/views/sys/settings/tabs/payment.vue"; +import SettingSafe from "/@/views/sys/settings/tabs/safe.vue"; import { useRoute, useRouter } from "vue-router"; import { ref } from "vue"; import { useSettingStore } from "/@/store/settings"; @@ -30,11 +34,11 @@ defineOptions({ name: "SysSettings", }); const settingsStore = useSettingStore(); -const activeKey = ref(""); +const activeKey = ref("base"); const route = useRoute(); const router = useRouter(); if (route.query.tab) { - activeKey.value = (route.query.tab as string) || ""; + activeKey.value = (route.query.tab as string) || "base"; } function onChange(value: string) { @@ -52,7 +56,7 @@ function onChange(value: string) { diff --git a/packages/ui/certd-client/vite.config.ts b/packages/ui/certd-client/vite.config.ts index 78b0f166..2bf69c22 100644 --- a/packages/ui/certd-client/vite.config.ts +++ b/packages/ui/certd-client/vite.config.ts @@ -14,36 +14,19 @@ import DefineOptions from "unplugin-vue-define-options/vite"; // 增加环境变量 _ process.env.VITE_APP_VERSION = require("./package.json").version; process.env.VITE_APP_BUILD_TIME = require("dayjs")().format("YYYY-M-D HH:mm:ss"); - -import { theme } from "ant-design-vue"; import * as https from "node:https"; -const { defaultAlgorithm, defaultSeed } = theme; - -const mapToken = defaultAlgorithm(defaultSeed); export default ({ command, mode }) => { console.log("args", command, mode); const env = loadEnv(mode, process.cwd()); - let devServerFs: any = {}; - let devAlias: any[] = []; - if (mode.startsWith("debug")) { - devAlias = [ - { find: /@fast-crud\/fast-crud\/dist/, replacement: path.resolve("../../fast-crud/src/") }, - // { find: /@fast-crud\/fast-crud$/, replacement: path.resolve("../../fast-crud/src/") }, - { find: /@fast-crud\/fast-extends\/dist/, replacement: path.resolve("../../fast-extends/src/") }, - // { find: /@fast-crud\/fast-extends$/, replacement: path.resolve("../../fast-extends/src/") }, - // { find: /@fast-crud\/ui-antdv$/, replacement: path.resolve("../../ui/ui-antdv/src/") }, - // { find: /@fast-crud\/ui-interface$/, replacement: path.resolve("../../ui/ui-interface/src/") } - { find: /@fast-crud\/ui-antdv4\/dist/, replacement: path.resolve("../../ui/ui-antdv4/src/") } - ]; - devServerFs = { - // 这里配置dev启动时读取的项目根目录 - allow: ["../../../"] - }; - console.log("devAlias", devAlias); - } + const devServerFs: any = {}; + const devAlias: any[] = []; + const base = "/"; + // if (mode.startsWith("dev")) { + // base = "/dev"; + // } return { - base: "/", + base: base, plugins: [ DefineOptions(), vueJsx(), @@ -52,12 +35,12 @@ export default ({ command, mode }) => { inject: { data: { title: env.VITE_APP_TITLE, - projectPath: env.VITE_APP_PROJECT_PATH - } - } + projectPath: env.VITE_APP_PROJECT_PATH, + }, + }, }), // 压缩build后的代码 - viteCompression() + viteCompression(), //主题色替换 //...configThemePlugin(true), // viteThemePlugin({ @@ -71,29 +54,29 @@ export default ({ command, mode }) => { drop: command === "build" ? ["debugger"] : [], pure: ["console.log", "debugger"], jsxFactory: "h", - jsxFragment: "Fragment" + jsxFragment: "Fragment", }, resolve: { alias: [...devAlias, { find: "/@", replacement: path.resolve("./src") }, { find: "/#", replacement: path.resolve("./types") }], - dedupe: ["vue"] + dedupe: ["vue"], }, optimizeDeps: { - include: ["ant-design-vue"] + include: ["ant-design-vue"], }, build: { rollupOptions: { - plugins: [visualizer()] - } + plugins: [visualizer()], + }, }, css: { preprocessorOptions: { less: { // 修改默认主题颜色,配置less变量 // modifyVars: generateModifyVars(), - javascriptEnabled: true + javascriptEnabled: true, // modifyVars: mapToken - } - } + }, + }, }, server: { host: "0.0.0.0", @@ -105,9 +88,9 @@ export default ({ command, mode }) => { //配套后端 https://github.com/fast-crud/fs-server-js target: "https://127.0.0.1:7002", //忽略证书 - agent: new https.Agent({ rejectUnauthorized: false }) - } - } - } + agent: new https.Agent({ rejectUnauthorized: false }), + }, + }, + }, }; }; diff --git a/packages/ui/certd-server/src/configuration.ts b/packages/ui/certd-server/src/configuration.ts index e854055f..bc27b11f 100644 --- a/packages/ui/certd-server/src/configuration.ts +++ b/packages/ui/certd-server/src/configuration.ts @@ -19,6 +19,7 @@ import * as libServer from '@certd/lib-server'; 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"; process.on('uncaughtException', error => { console.error('未捕获的异常:', error); // 在这里可以添加日志记录、发送错误通知等操作 @@ -77,6 +78,8 @@ export class MainConfiguration { this.app.useMiddleware([ //统一异常处理 GlobalExceptionMiddleware, + //站点隐藏 + HiddenMiddleware, //预览模式限制修改id<1000的数据 PreviewMiddleware, //授权处理 diff --git a/packages/ui/certd-server/src/controller/basic/unhidden-controller.ts b/packages/ui/certd-server/src/controller/basic/unhidden-controller.ts new file mode 100644 index 00000000..1e450e1d --- /dev/null +++ b/packages/ui/certd-server/src/controller/basic/unhidden-controller.ts @@ -0,0 +1,66 @@ +import {Body, Controller, Get, Inject, Post, Provide} from '@midwayjs/core'; +import {Constants, NotFoundException, ParamException, SysInstallInfo, SysSettingsService} from '@certd/lib-server'; +import {utils} from "@certd/basic"; +import {hiddenStatus, SafeService} from "../../modules/sys/settings/safe-service.js"; +import {IMidwayKoaContext} from "@midwayjs/koa"; + +const unhiddenHtml = ` + + +certd解除站点隐藏 + + +
+

解除站点隐藏

+
+请输入解除密码: +
+
+ + + +` + +@Provide() +@Controller('/api/unhidden') +export class HnhiddenController { + @Inject() + ctx: IMidwayKoaContext; + @Inject() + safeService: SafeService; + @Inject() + sysSettingsService: SysSettingsService; + + + @Post('/:randomPath', {summary: Constants.per.guest}) + async randomPath(@Body("password") password: any) { + await this.checkUnhiddenPath() + const hiddenSetting = await this.safeService.getHiddenSetting() + if (utils.hash.md5(password) === hiddenSetting.openPassword) { + //解锁 + hiddenStatus.isHidden = false; + const setting = await this.sysSettingsService.getSetting(SysInstallInfo) + const bindUrl = setting.bindUrl + //解锁成功,跳转回首页,redirect + this.ctx.response.redirect(bindUrl || "/"); + return + } else { + //密码错误 + throw new ParamException('解锁密码错误'); + } + } + + @Get('/:randomPath', {summary: Constants.per.guest}) + async unhiddenGet() { + await this.checkUnhiddenPath() + this.ctx.response.body = unhiddenHtml + } + + async checkUnhiddenPath() { + const hiddenSetting = await this.safeService.getHiddenSetting() + if (this.ctx.path != `/api/unhidden/${hiddenSetting.openPath}`) { + this.ctx.res.statusCode = 404 + throw new NotFoundException("Page not found") + } + } +} diff --git a/packages/ui/certd-server/src/controller/sys/settings/sys-safe-settings-controller.ts b/packages/ui/certd-server/src/controller/sys/settings/sys-safe-settings-controller.ts new file mode 100644 index 00000000..8bc963f3 --- /dev/null +++ b/packages/ui/certd-server/src/controller/sys/settings/sys-safe-settings-controller.ts @@ -0,0 +1,39 @@ +import {ALL, Body, Controller, Inject, Post, Provide} from '@midwayjs/core'; +import {BaseController, SysSafeSetting} from '@certd/lib-server'; +import {cloneDeep} from 'lodash-es'; +import {SafeService} from "../../../modules/sys/settings/safe-service.js"; + + +/** + */ +@Provide() +@Controller('/api/sys/settings/safe') +export class SysSettingsController extends BaseController { + @Inject() + safeService: SafeService; + + + + @Post("/get", { summary: "sys:settings:view" }) + async safeGet() { + const res = await this.safeService.getSafeSetting() + const clone:SysSafeSetting = cloneDeep(res); + delete clone.hidden?.openPassword; + return this.ok(clone); + } + + @Post("/save", { summary: "sys:settings:edit" }) + async safeSave(@Body(ALL) body: any) { + await this.safeService.saveSafeSetting(body); + return this.ok({}); + } + + /** + * 立即隐藏 + */ + @Post("/hidden", { summary: "sys:settings:edit" }) + async hiddenImmediate() { + await this.safeService.hiddenImmediately(); + return this.ok({}); + } +} diff --git a/packages/ui/certd-server/src/controller/sys/settings/sys-settings-controller.ts b/packages/ui/certd-server/src/controller/sys/settings/sys-settings-controller.ts index cc20be2e..14f58f5b 100644 --- a/packages/ui/certd-server/src/controller/sys/settings/sys-settings-controller.ts +++ b/packages/ui/certd-server/src/controller/sys/settings/sys-settings-controller.ts @@ -1,12 +1,19 @@ -import { ALL, Body, Controller, Inject, Post, Provide, Query } from '@midwayjs/core'; -import { CrudController, SysPrivateSettings, SysPublicSettings, SysSettingsEntity, SysSettingsService } from '@certd/lib-server'; -import { merge } from 'lodash-es'; -import { PipelineService } from '../../../modules/pipeline/service/pipeline-service.js'; -import { UserSettingsService } from '../../../modules/mine/service/user-settings-service.js'; -import { getEmailSettings } from '../../../modules/sys/settings/fix.js'; -import { http, logger, simpleNanoId } from '@certd/basic'; -import { CodeService } from '../../../modules/basic/service/code-service.js'; -import { SmsServiceFactory } from '../../../modules/basic/sms/factory.js'; +import {ALL, Body, Controller, Inject, Post, Provide, Query} from '@midwayjs/core'; +import { + CrudController, + SysPrivateSettings, + SysPublicSettings, + SysSafeSetting, + SysSettingsEntity, + SysSettingsService +} from '@certd/lib-server'; +import {cloneDeep, merge} from 'lodash-es'; +import {PipelineService} from '../../../modules/pipeline/service/pipeline-service.js'; +import {UserSettingsService} from '../../../modules/mine/service/user-settings-service.js'; +import {getEmailSettings} from '../../../modules/sys/settings/fix.js'; +import {http, logger, simpleNanoId, utils} from '@certd/basic'; +import {CodeService} from '../../../modules/basic/service/code-service.js'; +import {SmsServiceFactory} from '../../../modules/basic/sms/factory.js'; /** @@ -159,4 +166,29 @@ export class SysSettingsController extends CrudController { async getSmsTypeDefine(@Body('type') type: string) { return this.ok(SmsServiceFactory.getDefine(type)); } + + + + @Post("/safe/get", { summary: "sys:settings:view" }) + async safeGet() { + const res = await this.service.getSetting(SysSafeSetting); + const clone:SysSafeSetting = cloneDeep(res); + delete clone.hidden?.openPassword; + return this.ok(clone); + } + + @Post("/safe/save", { summary: "sys:settings:edit" }) + async safeSave(@Body(ALL) body: any) { + if(body.hidden.openPassword){ + body.hidden.openPassword = utils.hash.md5(body.hidden.openPassword); + } + const blankSetting = new SysSafeSetting() + const setting = await this.service.getSetting(SysSafeSetting); + const newSetting = merge(blankSetting,cloneDeep(setting), body); + if(newSetting.hidden?.enabled && !newSetting.hidden?.openPassword){ + throw new Error("首次设置需要填写解锁密码") + } + await this.service.saveSetting(blankSetting); + return this.ok({}); + } } diff --git a/packages/ui/certd-server/src/middleware/hidden.ts b/packages/ui/certd-server/src/middleware/hidden.ts new file mode 100644 index 00000000..7e135f7c --- /dev/null +++ b/packages/ui/certd-server/src/middleware/hidden.ts @@ -0,0 +1,54 @@ +import {Inject, Provide} from '@midwayjs/core'; +import {IMidwayKoaContext, IWebMiddleware, NextFunction} from '@midwayjs/koa'; +import {hiddenStatus, SafeService} from "../modules/sys/settings/safe-service.js"; +import {SiteOffException} from "@certd/lib-server"; + + +/** + * 隐藏环境 + */ +@Provide() +export class HiddenMiddleware implements IWebMiddleware { + @Inject() + hiddenService: SafeService; + + resolve() { + return async (ctx: IMidwayKoaContext, next: NextFunction) => { + + async function pass() { + hiddenStatus.updateRequestTime() + await next(); + } + + const hiddenSetting = await this.hiddenService.getHiddenSetting(); + if (!hiddenSetting || !hiddenSetting?.enabled) { + //未开启站点隐藏,直接通过 + return await pass() + } + + const req = ctx.request; + if (hiddenSetting.hiddenOpenApi === false && req.url.startsWith(`/api/v1/`) ) { + //不隐藏开放接口 + await next(); + return + } + + //判断当前是否是隐藏状态 + if (!hiddenStatus.isHidden) { + return await pass() + } + + //判断是否有解锁文件,如果有就返回true并删除文件 + if (hiddenStatus.hasUnHiddenFile()) { + //临时修改为未隐藏 + hiddenStatus.isHidden = false; + return await pass() + } + + if (req.url === `/api/unhidden/${hiddenSetting.openPath}`) { + return await pass(); + } + throw new SiteOffException('此站点已关闭'); + } + } +} diff --git a/packages/ui/certd-server/src/modules/auto/auto-a-init-site.ts b/packages/ui/certd-server/src/modules/auto/auto-a-init-site.ts index 052aa9b1..13fe23cc 100644 --- a/packages/ui/certd-server/src/modules/auto/auto-a-init-site.ts +++ b/packages/ui/certd-server/src/modules/auto/auto-a-init-site.ts @@ -4,6 +4,7 @@ import { UserService } from '../sys/authority/service/user-service.js'; import { PlusService, SysInstallInfo, SysPrivateSettings, SysSettingsService } from '@certd/lib-server'; import { nanoid } from 'nanoid'; import crypto from 'crypto'; +import {SafeService} from "../sys/settings/safe-service.js"; @Autoload() @Scope(ScopeEnum.Request, { allowDowngrade: true }) @@ -18,6 +19,8 @@ export class AutoAInitSite { sysSettingsService: SysSettingsService; @Inject() plusService: PlusService; + @Inject() + safeService: SafeService; @Init() async init() { @@ -57,6 +60,8 @@ export class AutoAInitSite { logger.error('授权许可验证失败', e); } + //加载站点隐藏配置 + await this.safeService.reloadHiddenStatus(true) logger.info('初始化站点完成'); } diff --git a/packages/ui/certd-server/src/modules/sys/settings/safe-service.ts b/packages/ui/certd-server/src/modules/sys/settings/safe-service.ts new file mode 100644 index 00000000..46fbbc2a --- /dev/null +++ b/packages/ui/certd-server/src/modules/sys/settings/safe-service.ts @@ -0,0 +1,110 @@ +import {Inject, Provide, Scope, ScopeEnum} from '@midwayjs/core'; +import {SiteHidden, SysSafeSetting, SysSettingsService} from "@certd/lib-server"; +import fs from "fs"; +import {logger, utils} from "@certd/basic"; +import {cloneDeep, merge} from "lodash-es"; + + +export class HiddenStatus { + + + isHidden = false; + lastRequestTime = 0; + intervalId: any = null; + + hasUnHiddenFile() { + if (fs.existsSync(`./data/.unhidden`)) { + fs.unlinkSync(`./data/.unhidden`) + return true + } + return false + } + + updateRequestTime() { + this.lastRequestTime = Date.now(); + } + + startCheck(autoHiddenTimes = 5) { + this.stopCheck() + this.intervalId = setInterval(() => { + //默认5分钟后自动隐藏 + if (!this.isHidden && Date.now() - this.lastRequestTime > 1000 * 60 * autoHiddenTimes) { + this.isHidden = true; + } + }, 1000 * 60) + } + + stopCheck() { + if (this.intervalId) { + clearInterval(this.intervalId) + this.intervalId = null + } + } + +} + +export const hiddenStatus = new HiddenStatus(); + + +@Provide('safeService') +@Scope(ScopeEnum.Request, {allowDowngrade: true}) +export class SafeService { + + @Inject() + sysSettingsService: SysSettingsService; + + + async reloadHiddenStatus(immediate = false) { + const hidden = await this.getHiddenSetting() + if (hidden.enabled) { + logger.error("启动站点隐藏"); + hiddenStatus.isHidden = false + if (immediate) { + hiddenStatus.isHidden = true; + } + const autoHiddenTimes = hidden.autoHiddenTimes || 5; + hiddenStatus.startCheck(autoHiddenTimes); + } else { + logger.error("关闭站点隐藏"); + hiddenStatus.isHidden = false; + hiddenStatus.stopCheck() + } + } + + + async getHiddenSetting(): Promise { + const safeSetting = await this.getSafeSetting() + return safeSetting.hidden || {enabled: false} + } + + async getSafeSetting() { + return await this.sysSettingsService.getSetting(SysSafeSetting) + } + + async hiddenImmediately() { + return hiddenStatus.isHidden = true + } + + async saveSafeSetting(body: SysSafeSetting) { + + // 更新hidden配置 + if (body.hidden.openPassword) { + body.hidden.openPassword = utils.hash.md5(body.hidden.openPassword); + } + const blankSetting = new SysSafeSetting() + const setting = await this.getSafeSetting() + const newSetting = merge(blankSetting, cloneDeep(setting), body); + if (newSetting.hidden?.enabled && !newSetting.hidden?.openPassword) { + throw new Error("首次设置需要填写解锁密码") + } + + if(isNaN(newSetting.hidden.autoHiddenTimes) || newSetting.hidden.autoHiddenTimes < 1){ + newSetting.hidden.autoHiddenTimes = 1 + } + + await this.sysSettingsService.saveSetting(newSetting); + + await this.reloadHiddenStatus(false) + + } +} diff --git a/packages/ui/certd-server/src/plugins/plugin-tencent/plugin/deploy-to-all/index.ts b/packages/ui/certd-server/src/plugins/plugin-tencent/plugin/deploy-to-all/index.ts index e40b637c..263de102 100644 --- a/packages/ui/certd-server/src/plugins/plugin-tencent/plugin/deploy-to-all/index.ts +++ b/packages/ui/certd-server/src/plugins/plugin-tencent/plugin/deploy-to-all/index.ts @@ -128,11 +128,11 @@ export class DeployCertToTencentAll extends AbstractTaskPlugin { }); let certId:string = null - if (typeof certId === 'string') { - certId = this.tencentCertId as string; - } else { + if (typeof certId === 'object') { //上传 certId = await this.uploadToTencent(access,this.tencentCertId as CertInfo); + } else { + certId = this.tencentCertId as string; } const params = { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 363562ec..d7f73fc7 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -46,7 +46,7 @@ importers: packages/core/acme-client: dependencies: '@certd/basic': - specifier: ^1.33.0 + specifier: ^1.33.2 version: link:../basic '@peculiar/x509': specifier: ^1.11.0 @@ -204,11 +204,11 @@ importers: packages/core/pipeline: dependencies: '@certd/basic': - specifier: ^1.33.0 + specifier: ^1.33.2 version: link:../basic '@certd/plus-core': - specifier: ^1.33.0 - version: link:../../pro/plus-core + specifier: ^1.33.2 + version: 1.33.2 dayjs: specifier: ^1.11.7 version: 1.11.13 @@ -412,7 +412,7 @@ importers: packages/libs/lib-k8s: dependencies: '@certd/basic': - specifier: ^1.33.0 + specifier: ^1.33.2 version: link:../../core/basic '@kubernetes/client-node': specifier: 0.21.0 @@ -452,17 +452,17 @@ importers: packages/libs/lib-server: dependencies: '@certd/acme-client': - specifier: ^1.33.0 + specifier: ^1.33.2 version: link:../../core/acme-client '@certd/basic': - specifier: ^1.33.0 + specifier: ^1.33.2 version: link:../../core/basic '@certd/pipeline': - specifier: ^1.33.0 + specifier: ^1.33.2 version: link:../../core/pipeline '@certd/plus-core': - specifier: ^1.33.0 - version: link:../../pro/plus-core + specifier: ^1.33.2 + version: 1.33.2 '@midwayjs/cache': specifier: ~3.14.0 version: 3.14.0 @@ -604,16 +604,16 @@ importers: packages/plugins/plugin-cert: dependencies: '@certd/acme-client': - specifier: ^1.33.0 + specifier: ^1.33.2 version: link:../../core/acme-client '@certd/basic': - specifier: ^1.33.0 + specifier: ^1.33.2 version: link:../../core/basic '@certd/pipeline': - specifier: ^1.33.0 + specifier: ^1.33.2 version: link:../../core/pipeline '@certd/plugin-lib': - specifier: ^1.33.0 + specifier: ^1.33.2 version: link:../plugin-lib '@google-cloud/publicca': specifier: ^1.3.0 @@ -680,10 +680,10 @@ importers: specifier: ^1.7.10 version: 1.8.0 '@certd/basic': - specifier: ^1.33.0 + specifier: ^1.33.2 version: link:../../core/basic '@certd/pipeline': - specifier: ^1.33.0 + specifier: ^1.33.2 version: link:../../core/pipeline '@kubernetes/client-node': specifier: 0.21.0 @@ -771,19 +771,19 @@ importers: packages/pro/commercial-core: dependencies: '@certd/basic': - specifier: ^1.33.0 + specifier: ^1.32.0 version: link:../../core/basic '@certd/lib-server': - specifier: ^1.33.0 + specifier: ^1.32.0 version: link:../../libs/lib-server '@certd/pipeline': - specifier: ^1.33.0 + specifier: ^1.32.0 version: link:../../core/pipeline '@certd/plugin-plus': - specifier: ^1.33.0 + specifier: ^1.32.0 version: link:../plugin-plus '@certd/plus-core': - specifier: ^1.33.0 + specifier: ^1.32.0 version: link:../plus-core '@midwayjs/core': specifier: ~3.20.3 @@ -868,22 +868,22 @@ importers: specifier: ^1.0.2 version: 1.0.2 '@certd/basic': - specifier: ^1.33.0 + specifier: ^1.32.0 version: link:../../core/basic '@certd/lib-k8s': - specifier: ^1.33.0 + specifier: ^1.32.0 version: link:../../libs/lib-k8s '@certd/pipeline': - specifier: ^1.33.0 + specifier: ^1.32.0 version: link:../../core/pipeline '@certd/plugin-cert': - specifier: ^1.33.0 + specifier: ^1.32.0 version: link:../../plugins/plugin-cert '@certd/plugin-lib': - specifier: ^1.33.0 + specifier: ^1.32.0 version: link:../../plugins/plugin-lib '@certd/plus-core': - specifier: ^1.33.0 + specifier: ^1.32.0 version: link:../plus-core ali-oss: specifier: ^6.21.0 @@ -980,7 +980,7 @@ importers: packages/pro/plus-core: dependencies: '@certd/basic': - specifier: ^1.33.0 + specifier: ^1.32.0 version: link:../../core/basic dayjs: specifier: ^1.11.7 @@ -1223,6 +1223,9 @@ importers: sortablejs: specifier: ^1.15.3 version: 1.15.6 + spark-md5: + specifier: ^3.0.2 + version: 3.0.2 tailwind-merge: specifier: ^3.0.2 version: 3.0.2 @@ -1267,10 +1270,10 @@ importers: version: 0.1.3(zod@3.24.2) devDependencies: '@certd/lib-iframe': - specifier: ^1.33.0 + specifier: ^1.33.2 version: link:../../libs/lib-iframe '@certd/pipeline': - specifier: ^1.33.0 + specifier: ^1.33.2 version: link:../../core/pipeline '@rollup/plugin-commonjs': specifier: ^25.0.7 @@ -1450,44 +1453,44 @@ importers: specifier: ^3.705.0 version: 3.758.0(aws-crt@1.25.3) '@certd/acme-client': - specifier: ^1.33.0 + specifier: ^1.33.2 version: link:../../core/acme-client '@certd/basic': - specifier: ^1.33.0 + specifier: ^1.33.2 version: link:../../core/basic '@certd/commercial-core': - specifier: ^1.33.0 - version: link:../../pro/commercial-core + specifier: ^1.33.2 + version: 1.33.2(better-sqlite3@11.8.1)(encoding@0.1.13)(mysql2@3.14.0)(pg@8.13.3)(reflect-metadata@0.2.2)(ts-node@10.9.2(@types/node@18.19.80)(typescript@5.8.2)) '@certd/jdcloud': - specifier: ^1.33.0 + specifier: ^1.33.2 version: link:../../libs/lib-jdcloud '@certd/lib-huawei': - specifier: ^1.33.0 + specifier: ^1.33.2 version: link:../../libs/lib-huawei '@certd/lib-k8s': - specifier: ^1.33.0 + specifier: ^1.33.2 version: link:../../libs/lib-k8s '@certd/lib-server': - specifier: ^1.33.0 + specifier: ^1.33.2 version: link:../../libs/lib-server '@certd/midway-flyway-js': - specifier: ^1.33.0 + specifier: ^1.33.2 version: link:../../libs/midway-flyway-js '@certd/pipeline': - specifier: ^1.33.0 + specifier: ^1.33.2 version: link:../../core/pipeline '@certd/plugin-cert': - specifier: ^1.33.0 + specifier: ^1.33.2 version: link:../../plugins/plugin-cert '@certd/plugin-lib': - specifier: ^1.33.0 + specifier: ^1.33.2 version: link:../../plugins/plugin-lib '@certd/plugin-plus': - specifier: ^1.33.0 - version: link:../../pro/plugin-plus + specifier: ^1.33.2 + version: 1.33.2(encoding@0.1.13) '@certd/plus-core': - specifier: ^1.33.0 - version: link:../../pro/plus-core + specifier: ^1.33.2 + version: 1.33.2 '@corsinvest/cv4pve-api-javascript': specifier: ^8.3.0 version: 8.3.0 @@ -2617,6 +2620,15 @@ packages: '@better-scroll/zoom@2.5.1': resolution: {integrity: sha512-aGvFY5ooeZWS4RcxQLD+pGLpQHQxpPy0sMZV3yadcd2QK53PK9gS4Dp+BYfRv8lZ4/P2LoNEhr6Wq1DN6+uPlA==} + '@certd/commercial-core@1.33.2': + resolution: {integrity: sha512-LTRvwRAkMEU+knG+/eA8QbyK3EE2Z2eyn4763ILadCY/qHLAWqxg7NUD+fwsuAoByvsr3l5qLPPOF73p5s1iEA==} + + '@certd/plugin-plus@1.33.2': + resolution: {integrity: sha512-x8qdJ1qtYfqVNBQ3uWJuFVYUHHnteoaH/3vnYKjgAnEAcosruZAz8h5RwwOjDhR+Vsdda/sSRdjlTCVDLidHZg==} + + '@certd/plus-core@1.33.2': + resolution: {integrity: sha512-CsD+/P3Ycne3KzxrZAkFXGpCO9ZQNY4p28WAA2XfKQ6sLIYLuuDBP18V/E7EygfJEZ/c9KJWMkYgx/4wGGvb6w==} + '@colors/colors@1.5.0': resolution: {integrity: sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==} engines: {node: '>=0.1.90'} @@ -14791,6 +14803,75 @@ snapshots: dependencies: '@better-scroll/core': 2.5.1 + '@certd/commercial-core@1.33.2(better-sqlite3@11.8.1)(encoding@0.1.13)(mysql2@3.14.0)(pg@8.13.3)(reflect-metadata@0.2.2)(ts-node@10.9.2(@types/node@18.19.80)(typescript@5.8.2))': + dependencies: + '@certd/basic': link:packages/core/basic + '@certd/lib-server': link:packages/libs/lib-server + '@certd/pipeline': link:packages/core/pipeline + '@certd/plugin-plus': 1.33.2(encoding@0.1.13) + '@certd/plus-core': 1.33.2 + '@midwayjs/core': 3.20.3 + '@midwayjs/koa': 3.20.3 + '@midwayjs/logger': 3.4.2 + '@midwayjs/typeorm': 3.20.3 + alipay-sdk: 4.13.0 + dayjs: 1.11.13 + typeorm: 0.3.21(better-sqlite3@11.8.1)(mysql2@3.14.0)(pg@8.13.3)(reflect-metadata@0.2.2)(ts-node@10.9.2(@types/node@18.19.80)(typescript@5.8.2)) + wechatpay-node-v3: 2.2.1 + transitivePeerDependencies: + - '@google-cloud/spanner' + - '@sap/hana-client' + - better-sqlite3 + - encoding + - hdb-pool + - ioredis + - mongodb + - mssql + - mysql2 + - oracledb + - pg + - pg-native + - pg-query-stream + - proxy-agent + - redis + - reflect-metadata + - sql.js + - sqlite3 + - supports-color + - ts-node + - typeorm-aurora-data-api-driver + + '@certd/plugin-plus@1.33.2(encoding@0.1.13)': + dependencies: + '@alicloud/pop-core': 1.8.0 + '@baiducloud/sdk': 1.0.2 + '@certd/basic': link:packages/core/basic + '@certd/lib-k8s': link:packages/libs/lib-k8s + '@certd/pipeline': link:packages/core/pipeline + '@certd/plugin-cert': link:packages/plugins/plugin-cert + '@certd/plugin-lib': link:packages/plugins/plugin-lib + '@certd/plus-core': 1.33.2 + ali-oss: 6.22.0 + baidu-aip-sdk: 4.16.16 + basic-ftp: 5.0.5 + cos-nodejs-sdk-v5: 2.14.6 + crypto-js: 4.2.0 + dayjs: 1.11.13 + form-data: 4.0.2 + https-proxy-agent: 7.0.6 + jsencrypt: 3.3.2 + qiniu: 7.14.0 + tencentcloud-sdk-nodejs: 4.0.1045(encoding@0.1.13) + transitivePeerDependencies: + - encoding + - proxy-agent + - supports-color + + '@certd/plus-core@1.33.2': + dependencies: + '@certd/basic': link:packages/core/basic + dayjs: 1.11.13 + '@colors/colors@1.5.0': optional: true