mirror of
https://github.com/certd/certd.git
synced 2026-04-24 04:17:25 +08:00
Merge remote-tracking branch 'origin/v2-dev' into v2-dev
This commit is contained in:
@@ -23,7 +23,6 @@ typeorm:
|
||||
database: './data/db-plus-dev-1.sqlite'
|
||||
|
||||
# plus server: 'http://127.0.0.1:11007'
|
||||
|
||||
account:
|
||||
server:
|
||||
baseUrl: 'http://localhost:1017/subject'
|
||||
|
||||
@@ -3,6 +3,54 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.37.1](https://github.com/certd/certd/compare/v1.37.0...v1.37.1) (2025-09-29)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* 修复某些情况下cname申请证书报错主域名不一致的bug ([2671781](https://github.com/certd/certd/commit/2671781e1bb0838981728d85eacf0e1a25a0fa48))
|
||||
|
||||
### Performance Improvements
|
||||
|
||||
* dns解析支持阿里esa ([9291fa6](https://github.com/certd/certd/commit/9291fa68aa7a88a05c2f888bf3048df36a8fbde3))
|
||||
|
||||
# [1.37.0](https://github.com/certd/certd/compare/v1.36.25...v1.37.0) (2025-09-28)
|
||||
|
||||
### Features
|
||||
|
||||
* dist打包前检查 ([8f6e5bd](https://github.com/certd/certd/commit/8f6e5bd24b3b65fbfcba36c08f532a3abad2d606))
|
||||
|
||||
## [1.36.25](https://github.com/certd/certd/compare/v1.36.24...v1.36.25) (2025-09-27)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* 固定midwayjs版本,修复ui-server import 错误的bug ([eb4d125](https://github.com/certd/certd/commit/eb4d125eaf4a41e88c752d0c68993829589f8f27))
|
||||
|
||||
## [1.36.24](https://github.com/certd/certd/compare/v1.36.23...v1.36.24) (2025-09-27)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* 修复 ui-server 加载失败问题 ([c2ccdbe](https://github.com/certd/certd/commit/c2ccdbec9dd08bca4688eeb2f34d0105eec43ba1))
|
||||
|
||||
### Performance Improvements
|
||||
|
||||
* 重置管理员密码同时会关闭验证码,防止验证码失效之后无法登录 ([03899d4](https://github.com/certd/certd/commit/03899d4d9c76fc2077dacc53ab88e2c9ca41af7c))
|
||||
|
||||
## [1.36.23](https://github.com/certd/certd/compare/v1.36.22...v1.36.23) (2025-09-26)
|
||||
|
||||
### Performance Improvements
|
||||
|
||||
* 开启子域名托管之后cname记录支持重置 ([54c8d62](https://github.com/certd/certd/commit/54c8d622437761d350db0f17e07f7517f1911211))
|
||||
* 验证码支持测试,登录验证码需要测试通过后才能开启 ([83e6476](https://github.com/certd/certd/commit/83e6476408090b741fabb1b542fb458d9a8b4134))
|
||||
|
||||
## [1.36.22](https://github.com/certd/certd/compare/v1.36.21...v1.36.22) (2025-09-23)
|
||||
|
||||
### Performance Improvements
|
||||
|
||||
* 7001绑定::地址 ([7188997](https://github.com/certd/certd/commit/7188997dd1979f1c10fa29b30221015e0bd5fe9e))
|
||||
* 公共cname支持权限校验 ([9cc5f0f](https://github.com/certd/certd/commit/9cc5f0f889d4362ff36e7a1f0e448e02d32ecee7))
|
||||
* dns支持新网域名解析 ([cf3a78e](https://github.com/certd/certd/commit/cf3a78e1145ff0505c87fbc485d9e731b1aa88a8))
|
||||
* gcore flush plugin ssl_id改为必填项 ([4b90972](https://github.com/certd/certd/commit/4b909723411c57505aa13b07d8699fb9ac77c937))
|
||||
|
||||
## [1.36.21](https://github.com/certd/certd/compare/v1.36.20...v1.36.21) (2025-09-15)
|
||||
|
||||
**Note:** Version bump only for package @certd/ui-server
|
||||
|
||||
@@ -5,9 +5,8 @@ const { Bootstrap } = require('@midwayjs/bootstrap');
|
||||
const DirectoryFileDetector = require('@midwayjs/core').DirectoryFileDetector;
|
||||
|
||||
const baseDir = process.cwd();
|
||||
const pipelineDir = baseDir + './node_modules/@certd/pipeline/dist';
|
||||
const customFileDetector = new DirectoryFileDetector({
|
||||
loadDir: [baseDir, pipelineDir],
|
||||
loadDir: [baseDir],
|
||||
});
|
||||
|
||||
module.exports = async () => {
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
ALTER TABLE cd_cname_record ADD COLUMN `main_domain` varchar(100);
|
||||
@@ -0,0 +1 @@
|
||||
ALTER TABLE cd_cname_record ADD COLUMN "main_domain" varchar(100);
|
||||
@@ -0,0 +1 @@
|
||||
ALTER TABLE cd_cname_record ADD COLUMN "main_domain" varchar(100);
|
||||
@@ -1,12 +1,14 @@
|
||||
{
|
||||
"name": "@certd/ui-server",
|
||||
"version": "1.36.21",
|
||||
"version": "1.37.1",
|
||||
"description": "fast-server base midway",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"start": "cross-env NODE_ENV=production node ./bootstrap.js",
|
||||
"dev": "cross-env NODE_ENV=local mwtsc --watch --run @midwayjs/mock/app",
|
||||
"dev-start": "mwtsc --watch --run @midwayjs/mock/app",
|
||||
"dc": "cd ../../../ && pnpm run dev",
|
||||
"dev": "cross-env NODE_ENV=local & pnpm run dev-start",
|
||||
"dev-commlocal": "cross-env NODE_ENV=dev-commlocal mwtsc --watch --run @midwayjs/mock/app",
|
||||
"dev-commpro": "cross-env NODE_ENV=dev-commpro mwtsc --watch --run @midwayjs/mock/app",
|
||||
"dev-pg": "cross-env NODE_ENV=dev-pg mwtsc --watch --run @midwayjs/mock/app",
|
||||
@@ -43,34 +45,34 @@
|
||||
"@aws-sdk/client-cloudfront": "^3.699.0",
|
||||
"@aws-sdk/client-iam": "^3.699.0",
|
||||
"@aws-sdk/client-s3": "^3.705.0",
|
||||
"@certd/acme-client": "^1.36.21",
|
||||
"@certd/basic": "^1.36.21",
|
||||
"@certd/commercial-core": "^1.36.21",
|
||||
"@certd/acme-client": "^1.37.1",
|
||||
"@certd/basic": "^1.37.1",
|
||||
"@certd/commercial-core": "^1.37.1",
|
||||
"@certd/cv4pve-api-javascript": "^8.4.2",
|
||||
"@certd/jdcloud": "^1.36.21",
|
||||
"@certd/lib-huawei": "^1.36.21",
|
||||
"@certd/lib-k8s": "^1.36.21",
|
||||
"@certd/lib-server": "^1.36.21",
|
||||
"@certd/midway-flyway-js": "^1.36.21",
|
||||
"@certd/pipeline": "^1.36.21",
|
||||
"@certd/plugin-cert": "^1.36.21",
|
||||
"@certd/plugin-lib": "^1.36.21",
|
||||
"@certd/plugin-plus": "^1.36.21",
|
||||
"@certd/plus-core": "^1.36.21",
|
||||
"@certd/jdcloud": "^1.37.1",
|
||||
"@certd/lib-huawei": "^1.37.1",
|
||||
"@certd/lib-k8s": "^1.37.1",
|
||||
"@certd/lib-server": "^1.37.1",
|
||||
"@certd/midway-flyway-js": "^1.37.1",
|
||||
"@certd/pipeline": "^1.37.1",
|
||||
"@certd/plugin-cert": "^1.37.1",
|
||||
"@certd/plugin-lib": "^1.37.1",
|
||||
"@certd/plugin-plus": "^1.37.1",
|
||||
"@certd/plus-core": "^1.37.1",
|
||||
"@huaweicloud/huaweicloud-sdk-cdn": "^3.1.120",
|
||||
"@huaweicloud/huaweicloud-sdk-core": "^3.1.120",
|
||||
"@koa/cors": "^5.0.0",
|
||||
"@midwayjs/bootstrap": "~3.20.3",
|
||||
"@midwayjs/cache": "~3.14.0",
|
||||
"@midwayjs/core": "~3.20.3",
|
||||
"@midwayjs/i18n": "~3.20.3",
|
||||
"@midwayjs/info": "~3.20.3",
|
||||
"@midwayjs/koa": "~3.20.3",
|
||||
"@midwayjs/logger": "~3.4.2",
|
||||
"@midwayjs/static-file": "~3.20.3",
|
||||
"@midwayjs/typeorm": "~3.20.3",
|
||||
"@midwayjs/upload": "~3.20.3",
|
||||
"@midwayjs/validate": "~3.20.3",
|
||||
"@midwayjs/bootstrap": "3.20.11",
|
||||
"@midwayjs/cache": "3.14.0",
|
||||
"@midwayjs/core": "3.20.11",
|
||||
"@midwayjs/i18n": "3.20.13",
|
||||
"@midwayjs/info": "3.20.13",
|
||||
"@midwayjs/koa": "3.20.13",
|
||||
"@midwayjs/logger": "3.4.2",
|
||||
"@midwayjs/static-file": "3.20.13",
|
||||
"@midwayjs/typeorm": "3.20.11",
|
||||
"@midwayjs/upload": "3.20.13",
|
||||
"@midwayjs/validate": "3.20.13",
|
||||
"@volcengine/openapi": "^1.28.1",
|
||||
"ali-oss": "^6.21.0",
|
||||
"axios": "^1.7.2",
|
||||
@@ -123,7 +125,7 @@
|
||||
"uuid": "^10.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@midwayjs/mock": "~3.20.3",
|
||||
"@midwayjs/mock": "3.20.11",
|
||||
"@types/ali-oss": "^6.16.11",
|
||||
"@types/cache-manager": "^4.0.6",
|
||||
"@types/jest": "^29.5.13",
|
||||
@@ -142,7 +144,7 @@
|
||||
"why-is-node-running": "^3.2.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18.0.0"
|
||||
"node": ">=20.0.0"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { ALL, Body, Controller, Inject, Post, Provide, Query } from '@midwayjs/core';
|
||||
import { AccessService, Constants } from '@certd/lib-server';
|
||||
import { AccessController } from '../../user/pipeline/access-controller.js';
|
||||
import { checkComm } from '@certd/plus-core';
|
||||
import { ALL, Body, Controller, Inject, Post, Provide, Query } from "@midwayjs/core";
|
||||
import { AccessService, Constants } from "@certd/lib-server";
|
||||
import { AccessController } from "../../user/pipeline/access-controller.js";
|
||||
|
||||
/**
|
||||
* 授权
|
||||
@@ -17,7 +16,7 @@ export class SysAccessController extends AccessController {
|
||||
}
|
||||
|
||||
getUserId() {
|
||||
checkComm();
|
||||
// checkComm();
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
import { BaseController } from '@certd/lib-server';
|
||||
import { ALL, Body, Controller, Inject, Post, Provide } from '@midwayjs/core';
|
||||
import { NetTestService } from '../../../modules/sys/nettest/nettest-service.js';
|
||||
|
||||
|
||||
@Provide()
|
||||
@Controller('/api/sys/nettest/')
|
||||
export class SysNetTestController extends BaseController {
|
||||
|
||||
@Inject()
|
||||
netTestService: NetTestService;
|
||||
|
||||
|
||||
@Post('/domainResolve', { summary: 'sys:settings:view' })
|
||||
public async domainResolve(@Body(ALL) body: { domain: string }) {
|
||||
|
||||
const { domain } = body;
|
||||
const result = await this.netTestService.domainResolve(domain);
|
||||
return this.ok(result);
|
||||
}
|
||||
|
||||
// ping
|
||||
@Post('/ping', { summary: 'sys:settings:view' })
|
||||
public async ping(@Body(ALL) body: { domain: string }) {
|
||||
|
||||
const { domain } = body;
|
||||
const result = await this.netTestService.ping(domain);
|
||||
return this.ok(result);
|
||||
}
|
||||
|
||||
// telnet
|
||||
@Post('/telnet', { summary: 'sys:settings:view' })
|
||||
public async telnet(@Body(ALL) body: { domain: string, port: number }) {
|
||||
|
||||
const { domain, port } = body;
|
||||
const result = await this.netTestService.telnet(domain, port);
|
||||
return this.ok(result);
|
||||
}
|
||||
|
||||
// telnet
|
||||
@Post('/serverInfo', { summary: 'sys:settings:view' })
|
||||
public async serverInfo() {
|
||||
|
||||
const result = await this.netTestService.serverInfo();
|
||||
return this.ok(result);
|
||||
}
|
||||
}
|
||||
@@ -192,4 +192,11 @@ export class SysSettingsController extends CrudController<SysSettingsService> {
|
||||
await this.service.saveSetting(blankSetting);
|
||||
return this.ok({});
|
||||
}
|
||||
|
||||
|
||||
@Post("/captchaTest", { summary: "sys:settings:edit" })
|
||||
async captchaTest(@Body(ALL) body: any) {
|
||||
await this.codeService.checkCaptcha(body)
|
||||
return this.ok({});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,56 +11,59 @@ import {
|
||||
import { AuthService } from "../../../modules/sys/authority/service/auth-service.js";
|
||||
import { checkPlus } from "@certd/plus-core";
|
||||
import { http, logger, utils } from "@certd/basic";
|
||||
import { TaskServiceBuilder } from "../../../modules/pipeline/service/getter/task-service-getter.js";
|
||||
|
||||
/**
|
||||
* Addon
|
||||
*/
|
||||
@Provide()
|
||||
@Controller('/api/addon')
|
||||
@Controller("/api/addon")
|
||||
export class AddonController extends CrudController<AddonService> {
|
||||
@Inject()
|
||||
service: AddonService;
|
||||
@Inject()
|
||||
authService: AuthService;
|
||||
@Inject()
|
||||
taskServiceBuilder:TaskServiceBuilder
|
||||
|
||||
getService(): AddonService {
|
||||
return this.service;
|
||||
}
|
||||
|
||||
@Post('/page', { summary: Constants.per.authOnly })
|
||||
@Post("/page", { summary: Constants.per.authOnly })
|
||||
async page(@Body(ALL) body) {
|
||||
body.query = body.query ?? {};
|
||||
delete body.query.userId;
|
||||
const buildQuery = qb => {
|
||||
qb.andWhere('user_id = :userId', { userId: this.getUserId() });
|
||||
qb.andWhere("user_id = :userId", { userId: this.getUserId() });
|
||||
};
|
||||
const res = await this.service.page({
|
||||
query: body.query,
|
||||
page: body.page,
|
||||
sort: body.sort,
|
||||
buildQuery,
|
||||
buildQuery
|
||||
});
|
||||
return this.ok(res);
|
||||
}
|
||||
|
||||
@Post('/list', { summary: Constants.per.authOnly })
|
||||
@Post("/list", { summary: Constants.per.authOnly })
|
||||
async list(@Body(ALL) body) {
|
||||
body.query = body.query ?? {};
|
||||
body.query.userId = this.getUserId();
|
||||
return super.list(body);
|
||||
}
|
||||
|
||||
@Post('/add', { summary: Constants.per.authOnly })
|
||||
@Post("/add", { summary: Constants.per.authOnly })
|
||||
async add(@Body(ALL) bean) {
|
||||
bean.userId = this.getUserId();
|
||||
const type = bean.type;
|
||||
const addonType = bean.addonType;
|
||||
if (! type || !addonType){
|
||||
throw new ValidateException('请选择Addon类型');
|
||||
if (!type || !addonType) {
|
||||
throw new ValidateException("请选择Addon类型");
|
||||
}
|
||||
const define: AddonDefine = this.service.getDefineByType(type,addonType);
|
||||
const define: AddonDefine = this.service.getDefineByType(type, addonType);
|
||||
if (!define) {
|
||||
throw new ValidateException('Addon类型不存在');
|
||||
throw new ValidateException("Addon类型不存在");
|
||||
}
|
||||
if (define.needPlus) {
|
||||
checkPlus();
|
||||
@@ -68,19 +71,19 @@ export class AddonController extends CrudController<AddonService> {
|
||||
return super.add(bean);
|
||||
}
|
||||
|
||||
@Post('/update', { summary: Constants.per.authOnly })
|
||||
@Post("/update", { summary: Constants.per.authOnly })
|
||||
async update(@Body(ALL) bean) {
|
||||
await this.service.checkUserId(bean.id, this.getUserId());
|
||||
const old = await this.service.info(bean.id);
|
||||
if (!old) {
|
||||
throw new ValidateException('Addon配置不存在');
|
||||
throw new ValidateException("Addon配置不存在");
|
||||
}
|
||||
if (old.type !== bean.type ) {
|
||||
if (old.type !== bean.type) {
|
||||
const addonType = old.type;
|
||||
const type = bean.type;
|
||||
const define: AddonDefine = this.service.getDefineByType(type,addonType);
|
||||
const define: AddonDefine = this.service.getDefineByType(type, addonType);
|
||||
if (!define) {
|
||||
throw new ValidateException('Addon类型不存在');
|
||||
throw new ValidateException("Addon类型不存在");
|
||||
}
|
||||
if (define.needPlus) {
|
||||
checkPlus();
|
||||
@@ -89,26 +92,27 @@ export class AddonController extends CrudController<AddonService> {
|
||||
delete bean.userId;
|
||||
return super.update(bean);
|
||||
}
|
||||
@Post('/info', { summary: Constants.per.authOnly })
|
||||
async info(@Query('id') id: number) {
|
||||
|
||||
@Post("/info", { summary: Constants.per.authOnly })
|
||||
async info(@Query("id") id: number) {
|
||||
await this.service.checkUserId(id, this.getUserId());
|
||||
return super.info(id);
|
||||
}
|
||||
|
||||
@Post('/delete', { summary: Constants.per.authOnly })
|
||||
async delete(@Query('id') id: number) {
|
||||
@Post("/delete", { summary: Constants.per.authOnly })
|
||||
async delete(@Query("id") id: number) {
|
||||
await this.service.checkUserId(id, this.getUserId());
|
||||
return super.delete(id);
|
||||
}
|
||||
|
||||
@Post('/define', { summary: Constants.per.authOnly })
|
||||
async define(@Query('type') type: string,@Query('addonType') addonType: string) {
|
||||
const notification = this.service.getDefineByType(type,addonType);
|
||||
@Post("/define", { summary: Constants.per.authOnly })
|
||||
async define(@Query("type") type: string, @Query("addonType") addonType: string) {
|
||||
const notification = this.service.getDefineByType(type, addonType);
|
||||
return this.ok(notification);
|
||||
}
|
||||
|
||||
@Post('/getTypeDict', { summary: Constants.per.authOnly })
|
||||
async getTypeDict(@Query('addonType') addonType: string) {
|
||||
@Post("/getTypeDict", { summary: Constants.per.authOnly })
|
||||
async getTypeDict(@Query("addonType") addonType: string) {
|
||||
const list: any = this.service.getDefineList(addonType);
|
||||
let dict = [];
|
||||
for (const item of list) {
|
||||
@@ -116,7 +120,7 @@ export class AddonController extends CrudController<AddonService> {
|
||||
value: item.name,
|
||||
label: item.title,
|
||||
needPlus: item.needPlus ?? false,
|
||||
icon: item.icon,
|
||||
icon: item.icon
|
||||
});
|
||||
}
|
||||
dict = dict.sort(a => {
|
||||
@@ -125,13 +129,13 @@ export class AddonController extends CrudController<AddonService> {
|
||||
return this.ok(dict);
|
||||
}
|
||||
|
||||
@Post('/simpleInfo', { summary: Constants.per.authOnly })
|
||||
async simpleInfo(@Query('addonType') addonType: string,@Query('id') id: number) {
|
||||
@Post("/simpleInfo", { summary: Constants.per.authOnly })
|
||||
async simpleInfo(@Query("addonType") addonType: string, @Query("id") id: number) {
|
||||
if (id === 0) {
|
||||
//获取默认
|
||||
const res = await this.service.getDefault(this.getUserId(),addonType);
|
||||
const res = await this.service.getDefault(this.getUserId(), addonType);
|
||||
if (!res) {
|
||||
throw new ValidateException('默认Addon配置不存在');
|
||||
throw new ValidateException("默认Addon配置不存在");
|
||||
}
|
||||
const simple = await this.service.getSimpleInfo(res.id);
|
||||
return this.ok(simple);
|
||||
@@ -141,27 +145,27 @@ export class AddonController extends CrudController<AddonService> {
|
||||
return this.ok(res);
|
||||
}
|
||||
|
||||
@Post('/getDefaultId', { summary: Constants.per.authOnly })
|
||||
async getDefaultId(@Query('addonType') addonType: string) {
|
||||
const res = await this.service.getDefault(this.getUserId(),addonType);
|
||||
@Post("/getDefaultId", { summary: Constants.per.authOnly })
|
||||
async getDefaultId(@Query("addonType") addonType: string) {
|
||||
const res = await this.service.getDefault(this.getUserId(), addonType);
|
||||
return this.ok(res?.id);
|
||||
}
|
||||
|
||||
@Post('/setDefault', { summary: Constants.per.authOnly })
|
||||
async setDefault(@Query('addonType') addonType: string,@Query('id') id: number) {
|
||||
@Post("/setDefault", { summary: Constants.per.authOnly })
|
||||
async setDefault(@Query("addonType") addonType: string, @Query("id") id: number) {
|
||||
await this.service.checkUserId(id, this.getUserId());
|
||||
const res = await this.service.setDefault(id, this.getUserId(),addonType);
|
||||
const res = await this.service.setDefault(id, this.getUserId(), addonType);
|
||||
return this.ok(res);
|
||||
}
|
||||
|
||||
|
||||
@Post('/options', { summary: Constants.per.authOnly })
|
||||
async options(@Query('addonType') addonType: string) {
|
||||
@Post("/options", { summary: Constants.per.authOnly })
|
||||
async options(@Query("addonType") addonType: string) {
|
||||
const res = await this.service.list({
|
||||
query: {
|
||||
userId: this.getUserId(),
|
||||
addonType
|
||||
},
|
||||
}
|
||||
});
|
||||
for (const item of res) {
|
||||
delete item.setting;
|
||||
@@ -170,7 +174,7 @@ export class AddonController extends CrudController<AddonService> {
|
||||
}
|
||||
|
||||
|
||||
@Post('/handle', { summary: Constants.per.authOnly })
|
||||
@Post("/handle", { summary: Constants.per.authOnly })
|
||||
async handle(@Body(ALL) body: AddonRequestHandleReq) {
|
||||
const userId = this.getUserId();
|
||||
let inputAddon = body.input.addon;
|
||||
@@ -178,21 +182,24 @@ export class AddonController extends CrudController<AddonService> {
|
||||
const oldEntity = await this.service.info(body.input.id);
|
||||
if (oldEntity) {
|
||||
if (oldEntity.userId !== userId) {
|
||||
throw new Error('addon not found');
|
||||
throw new Error("addon not found");
|
||||
}
|
||||
// const param: any = {
|
||||
// type: body.typeName,
|
||||
// setting: JSON.stringify(body.input.access),
|
||||
// };
|
||||
inputAddon = JSON.parse( oldEntity.setting)
|
||||
inputAddon = JSON.parse(oldEntity.setting);
|
||||
}
|
||||
}
|
||||
const serviceGetter = this.taskServiceBuilder.create({ userId });
|
||||
|
||||
const ctx = {
|
||||
http: http,
|
||||
logger:logger,
|
||||
utils:utils,
|
||||
}
|
||||
const addon = await newAddon(body.addonType,body.typeName, inputAddon,ctx);
|
||||
logger: logger,
|
||||
utils: utils,
|
||||
serviceGetter
|
||||
};
|
||||
const addon = await newAddon(body.addonType, body.typeName, inputAddon, ctx);
|
||||
const res = await addon.onRequest(body);
|
||||
return this.ok(res);
|
||||
}
|
||||
|
||||
@@ -85,10 +85,18 @@ export class CnameRecordController extends CrudController<CnameRecordService> {
|
||||
}
|
||||
|
||||
@Post('/verify', { summary: Constants.per.authOnly })
|
||||
async verify(@Body(ALL) body: { id: string }) {
|
||||
async verify(@Body(ALL) body: { id: number }) {
|
||||
const userId = this.getUserId();
|
||||
await this.service.checkUserId(body.id, userId);
|
||||
const res = await this.service.verify(body.id);
|
||||
return this.ok(res);
|
||||
}
|
||||
|
||||
@Post('/resetStatus', { summary: Constants.per.authOnly })
|
||||
async resetStatus(@Body(ALL) body: { id: number }) {
|
||||
const userId = this.getUserId();
|
||||
await this.service.checkUserId(body.id, userId);
|
||||
const res = await this.service.resetStatus(body.id);
|
||||
return this.ok(res);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import {ALL, Body, Controller, Inject, Post, Provide, Query} from '@midwayjs/core';
|
||||
import {Constants, CrudController} from '@certd/lib-server';
|
||||
import {SubDomainService} from "../../../modules/pipeline/service/sub-domain-service.js";
|
||||
import {DomainParser} from '@certd/plugin-cert/dist/dns-provider/domain-parser.js';
|
||||
import {DomainParser} from '@certd/plugin-cert';
|
||||
import { SubDomainsGetter } from '../../../modules/pipeline/service/getter/sub-domain-getter.js';
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Autoload, Config, Init, Inject, Provide, Scope, ScopeEnum } from '@midwayjs/core';
|
||||
import { IMidwayKoaContext, IWebMiddleware, NextFunction } from '@midwayjs/koa';
|
||||
import { CommonException } from '@certd/lib-server';
|
||||
import { CommonException, SysSettingsService } from "@certd/lib-server";
|
||||
import { UserService } from '../../modules/sys/authority/service/user-service.js';
|
||||
import { logger } from '@certd/basic';
|
||||
import {UserSettingsService} from "../../modules/mine/service/user-settings-service.js";
|
||||
@@ -17,6 +17,8 @@ export class ResetPasswdMiddleware implements IWebMiddleware {
|
||||
|
||||
@Inject()
|
||||
userSettingsService: UserSettingsService;
|
||||
@Inject()
|
||||
sysSettingsService: SysSettingsService;
|
||||
|
||||
@Config('system.resetAdminPasswd')
|
||||
private resetAdminPasswd: boolean;
|
||||
@@ -40,8 +42,12 @@ export class ResetPasswdMiddleware implements IWebMiddleware {
|
||||
userId: 1,
|
||||
key:"user.two.factor"
|
||||
})
|
||||
const publicSettings = await this.sysSettingsService.getPublicSettings()
|
||||
publicSettings.captchaEnabled = false
|
||||
await this.sysSettingsService.savePublicSettings(publicSettings);
|
||||
|
||||
const user = await this.userService.info(1);
|
||||
logger.info(`重置1号管理员用户的密码完成,2FA设置已删除,用户名:${user.username},新密码:${newPasswd},请在登录进去之后尽快修改密码`);
|
||||
logger.info(`重置1号管理员用户的密码完成,2FA设置已删除,验证码登录已禁用,用户名:${user.username},新密码:${newPasswd},请在登录进去之后尽快修改密码`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ import { Autoload, Init, Inject, Scope, ScopeEnum } from "@midwayjs/core";
|
||||
import { CertInfoService } from "../monitor/index.js";
|
||||
import { pipelineEmitter } from "@certd/pipeline";
|
||||
import { CertInfo, EVENT_CERT_APPLY_SUCCESS } from "@certd/plugin-cert";
|
||||
import { PipelineEvent } from "@certd/pipeline/dist/service/emit.js";
|
||||
import { PipelineEvent } from "@certd/pipeline";
|
||||
|
||||
@Autoload()
|
||||
@Scope(ScopeEnum.Request, { allowDowngrade: true })
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { Inject, Provide, Scope, ScopeEnum } from "@midwayjs/core";
|
||||
import { AddonService, SysSettingsService } from "@certd/lib-server";
|
||||
import { SysSettingsService } from "@certd/lib-server";
|
||||
import { logger } from "@certd/basic";
|
||||
import { ICaptchaAddon } from "../../../plugins/plugin-captcha/api.js";
|
||||
import { AddonGetterService } from "../../pipeline/service/addon-getter-service.js";
|
||||
|
||||
@Provide()
|
||||
@Scope(ScopeEnum.Request, { allowDowngrade: true })
|
||||
@@ -9,45 +10,48 @@ export class CaptchaService {
|
||||
@Inject()
|
||||
sysSettingsService: SysSettingsService;
|
||||
@Inject()
|
||||
addonService: AddonService;
|
||||
addonGetterService: AddonGetterService;
|
||||
|
||||
|
||||
async getCaptcha(captchaAddonId?:number){
|
||||
async getCaptcha(captchaAddonId?: number) {
|
||||
if (!captchaAddonId) {
|
||||
const settings = await this.sysSettingsService.getPublicSettings()
|
||||
captchaAddonId = settings.captchaAddonId ?? 0
|
||||
const settings = await this.sysSettingsService.getPublicSettings();
|
||||
captchaAddonId = settings.captchaAddonId ?? 0;
|
||||
}
|
||||
const addon:ICaptchaAddon = await this.addonService.getAddonById(captchaAddonId,true,0)
|
||||
const addon: ICaptchaAddon = await this.addonGetterService.getAddonById(captchaAddonId, true, 0, {
|
||||
type: "captcha",
|
||||
name: "image"
|
||||
});
|
||||
if (!addon) {
|
||||
throw new Error('验证码插件还未配置')
|
||||
throw new Error("验证码插件还未配置");
|
||||
}
|
||||
return await addon.getCaptcha()
|
||||
return await addon.getCaptcha();
|
||||
}
|
||||
|
||||
|
||||
async doValidate(opts:{form:any,must?:boolean,captchaAddonId?:number}){
|
||||
async doValidate(opts: { form: any, must?: boolean, captchaAddonId?: number }) {
|
||||
if (!opts.captchaAddonId) {
|
||||
const settings = await this.sysSettingsService.getPublicSettings()
|
||||
opts.captchaAddonId = settings.captchaAddonId ?? 0
|
||||
const settings = await this.sysSettingsService.getPublicSettings();
|
||||
opts.captchaAddonId = settings.captchaAddonId ?? 0;
|
||||
}
|
||||
const addon = await this.addonService.getById(opts.captchaAddonId,0)
|
||||
const addon = await this.addonGetterService.getById(opts.captchaAddonId, 0);
|
||||
if (!addon) {
|
||||
if (opts.must) {
|
||||
throw new Error('请先配置验证码插件');
|
||||
throw new Error("请先配置验证码插件");
|
||||
}
|
||||
logger.warn('验证码插件还未配置,忽略验证码校验')
|
||||
return true
|
||||
logger.warn("验证码插件还未配置,忽略验证码校验");
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!opts.form) {
|
||||
throw new Error('请输入验证码');
|
||||
throw new Error("请输入验证码");
|
||||
}
|
||||
const res = await addon.onValidate(opts.form)
|
||||
const res = await addon.onValidate(opts.form);
|
||||
if (!res) {
|
||||
throw new Error('验证码错误');
|
||||
throw new Error("验证码错误");
|
||||
}
|
||||
|
||||
return true
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ import { cache, isDev, randomNumber } from '@certd/basic';
|
||||
import { SysSettingsService, SysSiteInfo } from '@certd/lib-server';
|
||||
import { SmsServiceFactory } from '../sms/factory.js';
|
||||
import { ISmsService } from '../sms/api.js';
|
||||
import { CodeErrorException } from '@certd/lib-server/dist/basic/exception/code-error-exception.js';
|
||||
import { CodeErrorException } from '@certd/lib-server';
|
||||
import { EmailService } from './email-service.js';
|
||||
import { AccessService } from '@certd/lib-server';
|
||||
import { AccessSysGetter } from '@certd/lib-server';
|
||||
|
||||
@@ -4,7 +4,7 @@ import {In, Not, Repository} from 'typeorm';
|
||||
import {AccessService, BaseService} from '@certd/lib-server';
|
||||
import {DomainEntity} from '../entity/domain.js';
|
||||
import {SubDomainService} from "../../pipeline/service/sub-domain-service.js";
|
||||
import {DomainParser} from "@certd/plugin-cert/dist/dns-provider/domain-parser.js";
|
||||
import {DomainParser} from "@certd/plugin-cert";
|
||||
import {DomainVerifiers} from "@certd/plugin-cert";
|
||||
import { SubDomainsGetter } from '../../pipeline/service/getter/sub-domain-getter.js';
|
||||
import { CnameRecordService } from '../../cname/service/cname-record-service.js';
|
||||
|
||||
@@ -13,6 +13,8 @@ export class CnameRecordEntity {
|
||||
|
||||
@Column({ comment: '证书申请域名', length: 100 })
|
||||
domain: string;
|
||||
@Column({ comment: '主域名', name: 'main_domain', length: 100 })
|
||||
mainDomain:string;
|
||||
|
||||
@Column({ comment: '主机记录', name: 'host_record', length: 100 })
|
||||
hostRecord: string;
|
||||
|
||||
@@ -17,10 +17,11 @@ import { getAuthoritativeDnsResolver, walkTxtRecord } from "@certd/acme-client";
|
||||
import { CnameProviderService } from "./cname-provider-service.js";
|
||||
import { CnameProviderEntity } from "../entity/cname-provider.js";
|
||||
import { CommonDnsProvider } from "./common-provider.js";
|
||||
import { DomainParser } from "@certd/plugin-cert/dist/dns-provider/domain-parser.js";
|
||||
import { DomainParser } from "@certd/plugin-cert";
|
||||
import punycode from "punycode.js";
|
||||
import { SubDomainService } from "../../pipeline/service/sub-domain-service.js";
|
||||
import { SubDomainsGetter } from "../../pipeline/service/getter/sub-domain-getter.js";
|
||||
import { TaskServiceBuilder } from "../../pipeline/service/getter/task-service-getter.js";
|
||||
|
||||
type CnameCheckCacheValue = {
|
||||
validating: boolean;
|
||||
@@ -36,7 +37,7 @@ type CnameCheckCacheValue = {
|
||||
* 授权
|
||||
*/
|
||||
@Provide()
|
||||
@Scope(ScopeEnum.Request, {allowDowngrade: true})
|
||||
@Scope(ScopeEnum.Request, { allowDowngrade: true })
|
||||
export class CnameRecordService extends BaseService<CnameRecordEntity> {
|
||||
@InjectEntityModel(CnameRecordEntity)
|
||||
repository: Repository<CnameRecordEntity>;
|
||||
@@ -55,6 +56,10 @@ export class CnameRecordService extends BaseService<CnameRecordEntity> {
|
||||
@Inject()
|
||||
subDomainService: SubDomainService;
|
||||
|
||||
@Inject()
|
||||
taskServiceBuilder: TaskServiceBuilder;
|
||||
|
||||
|
||||
//@ts-ignore
|
||||
getRepository() {
|
||||
return this.repository;
|
||||
@@ -66,16 +71,16 @@ export class CnameRecordService extends BaseService<CnameRecordEntity> {
|
||||
*/
|
||||
async add(param: any): Promise<CnameRecordEntity> {
|
||||
if (!param.domain) {
|
||||
throw new ValidateException('域名不能为空');
|
||||
throw new ValidateException("域名不能为空");
|
||||
}
|
||||
if (!param.userId) {
|
||||
throw new ValidateException('userId不能为空');
|
||||
throw new ValidateException("userId不能为空");
|
||||
}
|
||||
if (param.domain.startsWith('*.')) {
|
||||
if (param.domain.startsWith("*.")) {
|
||||
param.domain = param.domain.substring(2);
|
||||
}
|
||||
param.domain = param.domain.trim()
|
||||
const info = await this.getRepository().findOne({where: {domain: param.domain, userId: param.userId}});
|
||||
param.domain = param.domain.trim();
|
||||
const info = await this.getRepository().findOne({ where: { domain: param.domain, userId: param.userId } });
|
||||
if (info) {
|
||||
return info;
|
||||
}
|
||||
@@ -85,63 +90,62 @@ export class CnameRecordService extends BaseService<CnameRecordEntity> {
|
||||
//获取默认的cnameProviderId
|
||||
cnameProvider = await this.cnameProviderService.getByPriority();
|
||||
if (cnameProvider == null) {
|
||||
throw new ValidateException('找不到CNAME服务,请先前往“系统管理->CNAME服务设置”添加CNAME服务');
|
||||
throw new ValidateException("找不到CNAME服务,请先前往“系统管理->CNAME服务设置”添加CNAME服务");
|
||||
}
|
||||
} else {
|
||||
cnameProvider = await this.cnameProviderService.info(param.cnameProviderId);
|
||||
}
|
||||
await this.cnameProviderChanged(param.userId, param, cnameProvider);
|
||||
|
||||
param.status = 'cname';
|
||||
const {id} = await super.add(param);
|
||||
param.status = "cname";
|
||||
const { id } = await super.add(param);
|
||||
return await this.info(id);
|
||||
}
|
||||
|
||||
private async cnameProviderChanged(userId: number, param: any, cnameProvider: CnameProviderEntity) {
|
||||
param.cnameProviderId = cnameProvider.id;
|
||||
|
||||
const subDomainGetter = new SubDomainsGetter(userId, this.subDomainService)
|
||||
const subDomainGetter = new SubDomainsGetter(userId, this.subDomainService);
|
||||
const domainParser = new DomainParser(subDomainGetter);
|
||||
|
||||
const realDomain = await domainParser.parse(param.domain);
|
||||
const prefix = param.domain.replace(realDomain, '');
|
||||
const prefix = param.domain.replace(realDomain, "");
|
||||
let hostRecord = `_acme-challenge.${prefix}`;
|
||||
if (hostRecord.endsWith('.')) {
|
||||
if (hostRecord.endsWith(".")) {
|
||||
hostRecord = hostRecord.substring(0, hostRecord.length - 1);
|
||||
}
|
||||
param.hostRecord = hostRecord;
|
||||
param.mainDomain = realDomain;
|
||||
|
||||
const randomKey = utils.id.simpleNanoId(6).toLowerCase();
|
||||
|
||||
let userIdHash = ""
|
||||
if(param.cnameProviderId < 0){
|
||||
//公共cname服务
|
||||
userIdHash = utils.hash.md5(`userId${userId}_${randomKey}`).substring(0, 10)
|
||||
}else{
|
||||
const installInfo = await this.sysSettingsService.getSetting<SysInstallInfo>(SysInstallInfo)
|
||||
userIdHash = utils.hash.md5(`${installInfo.siteId}_${randomKey}`).substring(0, 10)
|
||||
}
|
||||
const cnameKey = `${userIdHash}-${randomKey}`;
|
||||
const safeDomain = param.domain.replaceAll('.', '-');
|
||||
const userIdHex = utils.hash.toHex(userId);
|
||||
let userKeyHash = "";
|
||||
const installInfo = await this.sysSettingsService.getSetting<SysInstallInfo>(SysInstallInfo);
|
||||
userKeyHash = `${installInfo.siteId}_${userIdHex}_${randomKey}`;
|
||||
userKeyHash = utils.hash.md5(userKeyHash).substring(0, 10);
|
||||
logger.info(`userKeyHash:${userKeyHash},subjectId:${installInfo.siteId},randomKey:${randomKey},userIdHex:${userIdHex}`);
|
||||
const cnameKey = `${userKeyHash}-${userIdHex}-${randomKey}`;
|
||||
const safeDomain = param.domain.replaceAll(".", "-");
|
||||
param.recordValue = `${safeDomain}.${cnameKey}.${cnameProvider.domain}`;
|
||||
}
|
||||
|
||||
async update(param: any) {
|
||||
if (!param.id) {
|
||||
throw new ValidateException('id不能为空');
|
||||
throw new ValidateException("id不能为空");
|
||||
}
|
||||
|
||||
const old = await this.info(param.id);
|
||||
if (!old) {
|
||||
throw new ValidateException('数据不存在');
|
||||
throw new ValidateException("数据不存在");
|
||||
}
|
||||
if (old.domain !== param.domain) {
|
||||
throw new ValidateException('域名不允许修改');
|
||||
if (param.domain && old.domain !== param.domain) {
|
||||
throw new ValidateException("域名不允许修改");
|
||||
}
|
||||
if (old.cnameProviderId !== param.cnameProviderId) {
|
||||
if (param.cnameProviderId && old.cnameProviderId !== param.cnameProviderId) {
|
||||
const cnameProvider = await this.cnameProviderService.info(param.cnameProviderId);
|
||||
await this.cnameProviderChanged(old.userId, param, cnameProvider);
|
||||
param.status = 'cname';
|
||||
param.status = "cname";
|
||||
}
|
||||
return await super.update(param);
|
||||
}
|
||||
@@ -166,7 +170,7 @@ export class CnameRecordService extends BaseService<CnameRecordEntity> {
|
||||
} else {
|
||||
record.commonDnsProvider = new CommonDnsProvider({
|
||||
config: record.cnameProvider,
|
||||
plusService: this.plusService,
|
||||
plusService: this.plusService
|
||||
});
|
||||
}
|
||||
|
||||
@@ -175,19 +179,22 @@ export class CnameRecordService extends BaseService<CnameRecordEntity> {
|
||||
|
||||
async getByDomain(domain: string, userId: number, createOnNotFound = true) {
|
||||
if (!domain) {
|
||||
throw new ValidateException('domain不能为空');
|
||||
throw new ValidateException("domain不能为空");
|
||||
}
|
||||
if (userId == null) {
|
||||
throw new ValidateException('userId不能为空');
|
||||
throw new ValidateException("userId不能为空");
|
||||
}
|
||||
let record = await this.getRepository().findOne({where: {domain, userId}});
|
||||
let record = await this.getRepository().findOne({ where: { domain, userId } });
|
||||
if (record == null) {
|
||||
if (createOnNotFound) {
|
||||
record = await this.add({domain, userId});
|
||||
record = await this.add({ domain, userId });
|
||||
} else {
|
||||
throw new ValidateException(`找不到${domain}的CNAME记录`);
|
||||
}
|
||||
}
|
||||
|
||||
await this.fillMainDomain(record);
|
||||
|
||||
const provider = await this.cnameProviderService.info(record.cnameProviderId);
|
||||
if (provider == null) {
|
||||
throw new ValidateException(`找不到${domain}的CNAME服务`);
|
||||
@@ -196,25 +203,53 @@ export class CnameRecordService extends BaseService<CnameRecordEntity> {
|
||||
return {
|
||||
...record,
|
||||
cnameProvider: {
|
||||
...provider,
|
||||
} as CnameProvider,
|
||||
...provider
|
||||
} as CnameProvider
|
||||
} as CnameRecord;
|
||||
}
|
||||
|
||||
async fillMainDomain(record: CnameRecordEntity, update = true) {
|
||||
const notMainDomain = !record.mainDomain;
|
||||
const hasErrorMainDomain = record.mainDomain && !record.mainDomain.includes(".");
|
||||
if (notMainDomain || hasErrorMainDomain) {
|
||||
let domainPrefix = record.hostRecord.replace("_acme-challenge", "");
|
||||
if (domainPrefix.startsWith(".")) {
|
||||
domainPrefix = domainPrefix.substring(1);
|
||||
}
|
||||
|
||||
if (domainPrefix) {
|
||||
const prefixStr = domainPrefix + ".";
|
||||
record.mainDomain = record.domain.substring(prefixStr.length);
|
||||
}else{
|
||||
record.mainDomain = record.domain;
|
||||
}
|
||||
|
||||
if (update) {
|
||||
await this.update({
|
||||
id: record.id,
|
||||
mainDomain: record.mainDomain
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证是否配置好cname
|
||||
* @param id
|
||||
*/
|
||||
async verify(id: string) {
|
||||
async verify(id: number) {
|
||||
const bean = await this.info(id);
|
||||
if (!bean) {
|
||||
throw new ValidateException(`CnameRecord:${id} 不存在`);
|
||||
}
|
||||
if (bean.status === 'valid') {
|
||||
if (bean.status === "valid") {
|
||||
return true;
|
||||
}
|
||||
|
||||
const subDomainGetter = new SubDomainsGetter(bean.userId, this.subDomainService)
|
||||
await this.getByDomain(bean.domain, bean.userId);
|
||||
|
||||
const subDomainGetter = new SubDomainsGetter(bean.userId, this.subDomainService);
|
||||
const domainParser = new DomainParser(subDomainGetter);
|
||||
|
||||
const cacheKey = `cname.record.verify.${bean.id}`;
|
||||
@@ -224,7 +259,7 @@ export class CnameRecordService extends BaseService<CnameRecordEntity> {
|
||||
value = {
|
||||
validating: false,
|
||||
pass: false,
|
||||
startTime: new Date().getTime(),
|
||||
startTime: new Date().getTime()
|
||||
};
|
||||
}
|
||||
let ttl = 5 * 60 * 1000;
|
||||
@@ -246,15 +281,16 @@ export class CnameRecordService extends BaseService<CnameRecordEntity> {
|
||||
//公共CNAME
|
||||
return new CommonDnsProvider({
|
||||
config: cnameProvider,
|
||||
plusService: this.plusService,
|
||||
plusService: this.plusService
|
||||
});
|
||||
}
|
||||
|
||||
const serviceGetter = this.taskServiceBuilder.create({ userId: cnameProvider.userId });
|
||||
const access = await this.accessService.getById(cnameProvider.accessId, cnameProvider.userId);
|
||||
const context = {access, logger, http, utils, domainParser};
|
||||
const context = { access, logger, http, utils, domainParser, serviceGetter };
|
||||
const dnsProvider: IDnsProvider = await createDnsProvider({
|
||||
dnsProviderType: cnameProvider.dnsProviderType,
|
||||
context,
|
||||
context
|
||||
});
|
||||
return dnsProvider;
|
||||
};
|
||||
@@ -262,15 +298,15 @@ export class CnameRecordService extends BaseService<CnameRecordEntity> {
|
||||
const clearVerifyRecord = async () => {
|
||||
cache.delete(cacheKey);
|
||||
try {
|
||||
let dnsProvider = value.dnsProvider
|
||||
let dnsProvider = value.dnsProvider;
|
||||
if (!dnsProvider) {
|
||||
dnsProvider = await buildDnsProvider();
|
||||
}
|
||||
await dnsProvider.removeRecord({
|
||||
recordReq: value.recordReq,
|
||||
recordRes: value.recordRes,
|
||||
recordRes: value.recordRes
|
||||
});
|
||||
logger.info('删除CNAME的校验DNS记录成功');
|
||||
logger.info("删除CNAME的校验DNS记录成功");
|
||||
} catch (e) {
|
||||
logger.error(`删除CNAME的校验DNS记录失败, ${e.message},req:${JSON.stringify(value.recordReq)},recordRes:${JSON.stringify(value.recordRes)}`, e);
|
||||
}
|
||||
@@ -283,8 +319,8 @@ export class CnameRecordService extends BaseService<CnameRecordEntity> {
|
||||
if (value.startTime + ttl < new Date().getTime()) {
|
||||
logger.warn(`cname验证超时,停止检查,${bean.domain} ${testRecordValue}`);
|
||||
clearInterval(value.intervalId);
|
||||
await this.updateStatus(bean.id, 'timeout');
|
||||
await clearVerifyRecord()
|
||||
await this.updateStatus(bean.id, "timeout");
|
||||
await clearVerifyRecord();
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -295,7 +331,7 @@ export class CnameRecordService extends BaseService<CnameRecordEntity> {
|
||||
logger.info(`检查CNAME配置 ${fullDomain} ${testRecordValue}`);
|
||||
|
||||
//检查是否有重复的acme配置
|
||||
await this.checkRepeatAcmeChallengeRecords(fullDomain,bean.recordValue)
|
||||
await this.checkRepeatAcmeChallengeRecords(fullDomain, bean.recordValue);
|
||||
|
||||
// const txtRecords = await dns.promises.resolveTxt(fullDomain);
|
||||
// if (txtRecords.length) {
|
||||
@@ -312,9 +348,9 @@ export class CnameRecordService extends BaseService<CnameRecordEntity> {
|
||||
if (success) {
|
||||
clearInterval(value.intervalId);
|
||||
logger.info(`检测到CNAME配置,修改状态 ${fullDomain} ${testRecordValue}`);
|
||||
await this.updateStatus(bean.id, 'valid', "");
|
||||
await this.updateStatus(bean.id, "valid", "");
|
||||
value.pass = true;
|
||||
await clearVerifyRecord()
|
||||
await clearVerifyRecord();
|
||||
return success;
|
||||
}
|
||||
};
|
||||
@@ -325,88 +361,88 @@ export class CnameRecordService extends BaseService<CnameRecordEntity> {
|
||||
}
|
||||
|
||||
cache.set(cacheKey, value, {
|
||||
ttl: ttl,
|
||||
ttl: ttl
|
||||
});
|
||||
|
||||
const domain = await domainParser.parse(bean.recordValue);
|
||||
const fullRecord = bean.recordValue;
|
||||
const hostRecord = fullRecord.replace(`.${domain}`, '');
|
||||
const hostRecord = fullRecord.replace(`.${domain}`, "");
|
||||
const req = {
|
||||
domain: domain,
|
||||
fullRecord: fullRecord,
|
||||
hostRecord: hostRecord,
|
||||
type: 'TXT',
|
||||
value: testRecordValue,
|
||||
type: "TXT",
|
||||
value: testRecordValue
|
||||
};
|
||||
|
||||
const dnsProvider = await buildDnsProvider();
|
||||
if(dnsProvider.usePunyCode()){
|
||||
if (dnsProvider.usePunyCode()) {
|
||||
//是否需要中文转英文
|
||||
req.domain = dnsProvider.punyCodeEncode(req.domain)
|
||||
req.fullRecord = dnsProvider.punyCodeEncode(req.fullRecord)
|
||||
req.hostRecord = dnsProvider.punyCodeEncode(req.hostRecord)
|
||||
req.value = dnsProvider.punyCodeEncode(req.value)
|
||||
req.domain = dnsProvider.punyCodeEncode(req.domain);
|
||||
req.fullRecord = dnsProvider.punyCodeEncode(req.fullRecord);
|
||||
req.hostRecord = dnsProvider.punyCodeEncode(req.hostRecord);
|
||||
req.value = dnsProvider.punyCodeEncode(req.value);
|
||||
}
|
||||
const recordRes = await dnsProvider.createRecord(req);
|
||||
value.dnsProvider = dnsProvider;
|
||||
value.validating = true;
|
||||
value.recordReq = req;
|
||||
value.recordRes = recordRes;
|
||||
await this.updateStatus(bean.id, 'validating', "");
|
||||
await this.updateStatus(bean.id, "validating", "");
|
||||
|
||||
value.intervalId = setInterval(async () => {
|
||||
try {
|
||||
await checkRecordValue();
|
||||
} catch (e) {
|
||||
logger.error('检查cname出错:', e);
|
||||
logger.error("检查cname出错:", e);
|
||||
await this.updateError(bean.id, e.message);
|
||||
}
|
||||
}, 10000);
|
||||
}
|
||||
|
||||
async updateStatus(id: number, status: CnameRecordStatusType, error?: string) {
|
||||
const updated: any = {status}
|
||||
const updated: any = { status };
|
||||
if (error != null) {
|
||||
updated.error = error
|
||||
updated.error = error;
|
||||
}
|
||||
await this.getRepository().update(id, updated);
|
||||
}
|
||||
|
||||
async updateError(id: number, error: string) {
|
||||
await this.getRepository().update(id, {error});
|
||||
await this.getRepository().update(id, { error });
|
||||
}
|
||||
|
||||
async checkRepeatAcmeChallengeRecords(acmeRecordDomain: string,targetCnameDomain:string) {
|
||||
async checkRepeatAcmeChallengeRecords(acmeRecordDomain: string, targetCnameDomain: string) {
|
||||
|
||||
let dnsResolver = null
|
||||
try{
|
||||
dnsResolver = await getAuthoritativeDnsResolver(acmeRecordDomain)
|
||||
}catch (e) {
|
||||
logger.error(`获取${acmeRecordDomain}的权威DNS服务器失败,${e.message}`)
|
||||
return
|
||||
let dnsResolver = null;
|
||||
try {
|
||||
dnsResolver = await getAuthoritativeDnsResolver(acmeRecordDomain);
|
||||
} catch (e) {
|
||||
logger.error(`获取${acmeRecordDomain}的权威DNS服务器失败,${e.message}`);
|
||||
return;
|
||||
}
|
||||
let cnameRecords = []
|
||||
try{
|
||||
let cnameRecords = [];
|
||||
try {
|
||||
cnameRecords = await dnsResolver.resolveCname(acmeRecordDomain);
|
||||
}catch (e) {
|
||||
logger.error(`查询CNAME记录失败:${e.message}`)
|
||||
return
|
||||
} catch (e) {
|
||||
logger.error(`查询CNAME记录失败:${e.message}`);
|
||||
return;
|
||||
}
|
||||
targetCnameDomain = targetCnameDomain.toLowerCase()
|
||||
targetCnameDomain = punycode.toASCII(targetCnameDomain)
|
||||
targetCnameDomain = targetCnameDomain.toLowerCase();
|
||||
targetCnameDomain = punycode.toASCII(targetCnameDomain);
|
||||
if (cnameRecords.length > 0) {
|
||||
for (const cnameRecord of cnameRecords) {
|
||||
if(cnameRecord.toLowerCase() !== targetCnameDomain){
|
||||
if (cnameRecord.toLowerCase() !== targetCnameDomain) {
|
||||
//确保只有一个cname记录
|
||||
throw new Error(`${acmeRecordDomain}存在多个CNAME记录,请删除多余的CNAME记录:${cnameRecord}`)
|
||||
throw new Error(`${acmeRecordDomain}存在多个CNAME记录,请删除多余的CNAME记录:${cnameRecord}`);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// 确保权威服务器里面没有纯粹的TXT记录
|
||||
let txtRecords = []
|
||||
try{
|
||||
let txtRecords = [];
|
||||
try {
|
||||
const txtRecordRes = await dnsResolver.resolveTxt(acmeRecordDomain);
|
||||
|
||||
if (txtRecordRes && txtRecordRes.length > 0) {
|
||||
@@ -414,13 +450,13 @@ export class CnameRecordService extends BaseService<CnameRecordEntity> {
|
||||
logger.info(`TXT records: ${JSON.stringify(txtRecords)}`);
|
||||
txtRecords = txtRecords.concat(...txtRecordRes);
|
||||
}
|
||||
}catch (e) {
|
||||
logger.error(`查询Txt记录失败:${e.message}`)
|
||||
} catch (e) {
|
||||
logger.error(`查询Txt记录失败:${e.message}`);
|
||||
}
|
||||
|
||||
if (txtRecords.length === 0) {
|
||||
//如果权威服务器中查不到txt,无需继续检查
|
||||
return
|
||||
return;
|
||||
}
|
||||
if (cnameRecords.length > 0) {
|
||||
// 从cname记录中获取txt记录
|
||||
@@ -429,11 +465,18 @@ export class CnameRecordService extends BaseService<CnameRecordEntity> {
|
||||
if (res.length > 0) {
|
||||
for (const txtRecord of txtRecords) {
|
||||
if (!res.includes(txtRecord)) {
|
||||
throw new Error(`${acmeRecordDomain}存在多个TXT记录,请删除多余的TXT记录:${txtRecord}`)
|
||||
throw new Error(`${acmeRecordDomain}存在多个TXT记录,请删除多余的TXT记录:${txtRecord}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
async resetStatus(id: number) {
|
||||
if (!id) {
|
||||
throw new ValidateException("id不能为空");
|
||||
}
|
||||
await this.getRepository().update(id, { status: "cname", mainDomain: "" });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,12 +11,12 @@ import {
|
||||
import { RoleService } from "../../sys/authority/service/role-service.js";
|
||||
import { UserEntity } from "../../sys/authority/entity/user.js";
|
||||
import { cache, utils } from "@certd/basic";
|
||||
import { LoginErrorException } from "@certd/lib-server/dist/basic/exception/login-error-exception.js";
|
||||
import { LoginErrorException } from "@certd/lib-server";
|
||||
import { CodeService } from "../../basic/service/code-service.js";
|
||||
import { TwoFactorService } from "../../mine/service/two-factor-service.js";
|
||||
import { UserSettingsService } from "../../mine/service/user-settings-service.js";
|
||||
import { isPlus } from "@certd/plus-core";
|
||||
import { AddonService } from "@certd/lib-server/dist/user/addon/service/addon-service.js";
|
||||
import { AddonService } from "@certd/lib-server";
|
||||
|
||||
/**
|
||||
* 系统用户
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
import { Inject, Provide, Scope, ScopeEnum } from "@midwayjs/core";
|
||||
import { http, logger, utils } from "@certd/basic";
|
||||
import { TaskServiceBuilder } from "./getter/task-service-getter.js";
|
||||
import { AddonService, newAddon, PermissionException, ValidateException } from "@certd/lib-server";
|
||||
|
||||
/**
|
||||
* Addon
|
||||
*/
|
||||
@Provide()
|
||||
@Scope(ScopeEnum.Request, { allowDowngrade: true })
|
||||
export class AddonGetterService {
|
||||
|
||||
@Inject()
|
||||
taskServiceBuilder: TaskServiceBuilder;
|
||||
@Inject()
|
||||
addonService: AddonService;
|
||||
|
||||
|
||||
async getAddonById(id: any, checkUserId: boolean, userId?: number, defaultAddon?:{type:string,name:string} ): Promise<any> {
|
||||
const serviceGetter = this.taskServiceBuilder.create({
|
||||
userId
|
||||
});
|
||||
const ctx = {
|
||||
http,
|
||||
logger,
|
||||
utils,
|
||||
serviceGetter
|
||||
}
|
||||
|
||||
if (!id) {
|
||||
if (!defaultAddon) {
|
||||
return null;
|
||||
}
|
||||
return await newAddon(defaultAddon.type, defaultAddon.name, {}, ctx);
|
||||
}
|
||||
const entity = await this.addonService.info(id);
|
||||
if (entity == null) {
|
||||
if (!defaultAddon) {
|
||||
return null;
|
||||
}
|
||||
return await newAddon(defaultAddon.type, defaultAddon.name, {}, ctx);
|
||||
}
|
||||
if (checkUserId) {
|
||||
if (userId == null) {
|
||||
throw new ValidateException("userId不能为空");
|
||||
}
|
||||
if (userId !== entity.userId) {
|
||||
throw new PermissionException("您对该Addon无访问权限");
|
||||
}
|
||||
}
|
||||
|
||||
const setting = JSON.parse(entity.setting ?? "{}");
|
||||
const input = {
|
||||
id: entity.id,
|
||||
...setting
|
||||
};
|
||||
|
||||
return await newAddon(entity.addonType, entity.type, input, ctx);
|
||||
}
|
||||
|
||||
async getById(id: any, userId: number): Promise<any> {
|
||||
return await this.getAddonById(id, true, userId);
|
||||
}
|
||||
|
||||
}
|
||||
+15
-5
@@ -10,6 +10,9 @@ import { DomainVerifierGetter } from "./domain-verifier-getter.js";
|
||||
import { DomainService } from "../../../cert/service/domain-service.js";
|
||||
import { SubDomainService } from "../sub-domain-service.js";
|
||||
|
||||
const serviceNames = [
|
||||
'ocrService',
|
||||
]
|
||||
export class TaskServiceGetter implements IServiceGetter{
|
||||
private userId: number;
|
||||
private appCtx : IMidwayContainer;
|
||||
@@ -30,7 +33,13 @@ export class TaskServiceGetter implements IServiceGetter{
|
||||
} else if (serviceName === 'domainVerifierGetter') {
|
||||
return await this.getDomainVerifierGetter() as T
|
||||
}else{
|
||||
throw new Error(`service ${serviceName} not found`)
|
||||
if(!serviceNames.includes(serviceName)){
|
||||
throw new Error(`${serviceName} not in whitelist`)
|
||||
}
|
||||
const service = await this.appCtx.getAsync(serviceName)
|
||||
if (! service){
|
||||
throw new Error(`${serviceName} not found`)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,6 +53,7 @@ export class TaskServiceGetter implements IServiceGetter{
|
||||
return new AccessGetter(this.userId, accessService.getById.bind(accessService));
|
||||
}
|
||||
|
||||
|
||||
async getCnameProxyService(): Promise<CnameProxyService> {
|
||||
const cnameRecordService:CnameRecordService = await this.appCtx.getAsync("cnameRecordService")
|
||||
return new CnameProxyService(this.userId, cnameRecordService.getWithAccessByDomain.bind(cnameRecordService));
|
||||
@@ -59,10 +69,6 @@ export class TaskServiceGetter implements IServiceGetter{
|
||||
return new DomainVerifierGetter(this.userId, domainService);
|
||||
}
|
||||
}
|
||||
export type TaskServiceCreateReq = {
|
||||
userId: number;
|
||||
}
|
||||
|
||||
@Provide()
|
||||
@Scope(ScopeEnum.Request, { allowDowngrade: true })
|
||||
export class TaskServiceBuilder {
|
||||
@@ -75,6 +81,10 @@ export class TaskServiceBuilder {
|
||||
}
|
||||
}
|
||||
|
||||
export type TaskServiceCreateReq = {
|
||||
userId: number;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,231 @@
|
||||
import { Provide, Scope, ScopeEnum } from '@midwayjs/core';
|
||||
import { http, logger, utils } from '@certd/basic';
|
||||
|
||||
// 使用@certd/basic包中已有的utils.sp.spawn函数替代自定义的asyncExec
|
||||
// 该函数已经内置了Windows系统编码问题的解决方案
|
||||
|
||||
export type NetTestResult = {
|
||||
success: boolean; //是否成功
|
||||
message: string; //结果
|
||||
testLog: string; //测试日志
|
||||
error?: string; //执行错误信息
|
||||
}
|
||||
|
||||
@Provide('nettestService')
|
||||
@Scope(ScopeEnum.Request, { allowDowngrade: true })
|
||||
export class NetTestService {
|
||||
/**
|
||||
* 执行Telnet测试
|
||||
* @param domain 域名
|
||||
* @param port 端口
|
||||
* @returns 测试结果
|
||||
*/
|
||||
async telnet(domain: string, port: number): Promise<NetTestResult> {
|
||||
try {
|
||||
let command = '';
|
||||
|
||||
if (this.isWindows()) {
|
||||
// Windows系统使用PowerShell执行测试,避免输入重定向问题
|
||||
// 使用PowerShell的Test-NetConnection命令进行端口测试
|
||||
command = `powershell -Command "& { $result = Test-NetConnection -ComputerName ${domain} -Port ${port} -InformationLevel Quiet; if ($result) { Write-Host '端口连接成功' } else { Write-Host '端口连接失败' } }"`;
|
||||
} else {
|
||||
// Linux系统使用nc命令进行端口测试
|
||||
command = `nc -zv -w 5 ${domain} ${port} 2>&1`;
|
||||
}
|
||||
|
||||
// 使用utils.sp.spawn执行命令,它会自动处理Windows编码问题
|
||||
const output = await utils.sp.spawn({
|
||||
cmd: command,
|
||||
logger: undefined // 可以根据需要传入logger
|
||||
});
|
||||
|
||||
// 判断测试是否成功
|
||||
const success = this.isWindows()
|
||||
? output.includes('端口连接成功')
|
||||
: output.includes('Connected to');
|
||||
|
||||
// 处理结果
|
||||
return {
|
||||
success,
|
||||
message: success ? '端口连接测试成功' : '端口连接测试失败',
|
||||
testLog: output,
|
||||
};
|
||||
|
||||
} catch (error) {
|
||||
return {
|
||||
success: false,
|
||||
message: 'Telnet测试执行失败',
|
||||
testLog: error.stdout || error.stderr || error?.message || String(error),
|
||||
error: error.stderr || error?.message || String(error),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行Ping测试
|
||||
* @param domain 域名
|
||||
* @returns 测试结果
|
||||
*/
|
||||
async ping(domain: string): Promise<NetTestResult> {
|
||||
try {
|
||||
let command = '';
|
||||
|
||||
if (this.isWindows()) {
|
||||
// Windows系统ping命令,发送4个包
|
||||
command = `ping -n 4 ${domain}`;
|
||||
} else {
|
||||
// Linux系统ping命令,发送4个包
|
||||
command = `ping -c 4 ${domain}`;
|
||||
}
|
||||
|
||||
// 使用utils.sp.spawn执行命令
|
||||
const output = await utils.sp.spawn({
|
||||
cmd: command,
|
||||
logger: undefined
|
||||
});
|
||||
|
||||
// 判断测试是否成功
|
||||
const success = this.isWindows()
|
||||
? output.includes('TTL=')
|
||||
: output.includes('time=');
|
||||
|
||||
return {
|
||||
success,
|
||||
message: success ? 'Ping测试成功' : 'Ping测试失败',
|
||||
testLog: output,
|
||||
};
|
||||
|
||||
} catch (error) {
|
||||
const errorMessage = error instanceof Error ? error.message : String(error);
|
||||
return {
|
||||
success: false,
|
||||
message: 'Ping测试执行失败',
|
||||
testLog: error.stderr|| error.stdout || errorMessage,
|
||||
error: errorMessage
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
private isWindows() {
|
||||
return process.platform === 'win32';
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行域名解析测试
|
||||
* @param domain 域名
|
||||
* @returns 解析结果
|
||||
*/
|
||||
async domainResolve(domain: string): Promise<NetTestResult> {
|
||||
try {
|
||||
let command = '';
|
||||
if (this.isWindows()) {
|
||||
// Windows系统使用nslookup命令
|
||||
command = `nslookup ${domain}`;
|
||||
} else {
|
||||
// Linux系统优先使用dig命令,如果没有则回退到nslookup
|
||||
command = `which dig > /dev/null && dig ${domain} || nslookup ${domain}`;
|
||||
}
|
||||
|
||||
// 使用utils.sp.spawn执行命令
|
||||
const output = await utils.sp.spawn({
|
||||
cmd: command,
|
||||
logger: undefined
|
||||
});
|
||||
|
||||
// 判断测试是否成功
|
||||
const success = output.includes('Address:') || output.includes('IN A') || output.includes('IN AAAA') ||
|
||||
(this.isWindows() && output.includes('Name:'));
|
||||
|
||||
return {
|
||||
success,
|
||||
message: success ? '域名解析测试成功' : '域名解析测试失败',
|
||||
testLog: output,
|
||||
};
|
||||
} catch (error) {
|
||||
const errorMessage = error instanceof Error ? error.message : String(error);
|
||||
return {
|
||||
success: false,
|
||||
message: '域名解析测试执行失败',
|
||||
testLog: error.stdoout || error.stderr || errorMessage,
|
||||
error: errorMessage
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
async getLocalIP(): Promise<string[]> {
|
||||
try {
|
||||
const output = await utils.sp.spawn({
|
||||
cmd: 'ip a | grep \'inet \' | grep -v \'127.0.0.1\' | awk \'{print $2}\' | cut -d/ -f1',
|
||||
logger: undefined
|
||||
});
|
||||
// 去除 inet 前缀
|
||||
let ips = output.trim().replace(/inet /g, '');
|
||||
return ips.split('\n').filter(ip => ip.length > 0);
|
||||
} catch (error) {
|
||||
return [error instanceof Error ? error.message : String(error)];
|
||||
}
|
||||
}
|
||||
|
||||
async getPublicIP(): Promise<string[]> {
|
||||
try {
|
||||
const res = await http.request({
|
||||
url:"https://ipinfo.io/ip",
|
||||
method:"GET",
|
||||
})
|
||||
return[res]
|
||||
} catch (error) {
|
||||
return [error instanceof Error ? error.message : String(error)]
|
||||
}
|
||||
}
|
||||
|
||||
async getDNSservers(): Promise<string[]> {
|
||||
let dnsServers: string[] = [];
|
||||
try {
|
||||
const output = await utils.sp.spawn({
|
||||
cmd: 'cat /etc/resolv.conf | grep nameserver | awk \'{print $2}\'',
|
||||
logger: undefined
|
||||
});
|
||||
dnsServers = output.trim().split('\n');
|
||||
} catch (error) {
|
||||
dnsServers = [error instanceof Error ? error.message : String(error)];
|
||||
}
|
||||
try{
|
||||
/**
|
||||
* /app # cat /etc/resolv.conf | grep "ExtServers"
|
||||
# ExtServers: [223.5.5.5 223.6.6.6]
|
||||
*/
|
||||
const extDnsServers = await utils.sp.spawn({
|
||||
cmd: 'cat /etc/resolv.conf | grep "ExtServers"',
|
||||
logger: undefined
|
||||
});
|
||||
const line = extDnsServers.trim()
|
||||
if (line.includes('ExtServers') && line.includes('[')) {
|
||||
const extDns = line.substring(line.indexOf('[') + 1, line.indexOf(']')).split(' ');
|
||||
const dnsList = extDns.map(item=>`Ext:${item}`)
|
||||
dnsServers = dnsServers.concat(dnsList);
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error('获取DNS ExtServers 服务器失败', error);
|
||||
// dnsServers.push(error instanceof Error ? error.message : String(error));
|
||||
}
|
||||
return dnsServers;
|
||||
}
|
||||
/**
|
||||
* 获取服务器信息(包括本地IP、外网IP和DNS服务器)
|
||||
* @returns 服务器信息
|
||||
*/
|
||||
async serverInfo(): Promise<any> {
|
||||
|
||||
const res = {
|
||||
localIP: [],
|
||||
publicIP: [],
|
||||
dnsServers: [],
|
||||
}
|
||||
|
||||
res.localIP = await this.getLocalIP();
|
||||
res.publicIP = await this.getPublicIP();
|
||||
res.dnsServers = await this.getDNSservers();
|
||||
return res
|
||||
}
|
||||
}
|
||||
@@ -36,3 +36,4 @@ export * from './plugin-apisix/index.js'
|
||||
export * from './plugin-dokploy/index.js'
|
||||
export * from './plugin-godaddy/index.js'
|
||||
export * from './plugin-captcha/index.js'
|
||||
export * from './plugin-xinnet/index.js'
|
||||
|
||||
+105
@@ -0,0 +1,105 @@
|
||||
import { IAccessService } from '@certd/pipeline';
|
||||
import { AbstractDnsProvider, CreateRecordOptions, IsDnsProvider, RemoveRecordOptions } from '@certd/plugin-cert';
|
||||
|
||||
import { AliesaAccess, AliyunAccess, AliyunClientV2 } from '@certd/plugin-lib';
|
||||
|
||||
@IsDnsProvider({
|
||||
name: 'aliesa',
|
||||
title: '阿里ESA',
|
||||
desc: '阿里ESA DNS解析',
|
||||
accessType: 'aliesa',
|
||||
icon: 'svg:icon-aliyun',
|
||||
order: 0,
|
||||
})
|
||||
export class AliesaDnsProvider extends AbstractDnsProvider {
|
||||
|
||||
|
||||
client: AliyunClientV2
|
||||
async onInstance() {
|
||||
const access: AliesaAccess = this.ctx.access as AliesaAccess
|
||||
const accessGetter = await this.ctx.serviceGetter.get("accessService") as IAccessService
|
||||
const aliAccess = await accessGetter.getById(access.accessId) as AliyunAccess
|
||||
const endpoint = `esa.${access.region}.aliyuncs.com`
|
||||
this.client = aliAccess.getClient(endpoint)
|
||||
}
|
||||
|
||||
|
||||
async getSiteItem(domain: string) {
|
||||
const ret = await this.client.doRequest({
|
||||
// 接口名称
|
||||
action: "ListSites",
|
||||
// 接口版本
|
||||
version: "2024-09-10",
|
||||
// 接口协议
|
||||
protocol: "HTTPS",
|
||||
// 接口 HTTP 方法
|
||||
method: "GET",
|
||||
authType: "AK",
|
||||
style: "RPC",
|
||||
data: {
|
||||
query: {
|
||||
SiteName: domain,
|
||||
// ["SiteSearchType"] = "exact";
|
||||
SiteSearchType: "exact",
|
||||
AccessType: "NS"
|
||||
}
|
||||
}
|
||||
})
|
||||
const list = ret.Sites
|
||||
if (list?.length === 0) {
|
||||
throw new Error(`阿里云ESA中不存在此域名站点:${domain},请确认域名已添加到ESA中,且为NS接入方式`);
|
||||
}
|
||||
return list[0]
|
||||
|
||||
}
|
||||
|
||||
async createRecord(options: CreateRecordOptions): Promise<any> {
|
||||
const { fullRecord, value, type, domain } = options;
|
||||
this.logger.info('添加域名解析:', fullRecord, value, domain);
|
||||
|
||||
|
||||
const siteItem = await this.getSiteItem(domain)
|
||||
const siteId = siteItem.SiteId
|
||||
|
||||
|
||||
const res = await this.client.doRequest({
|
||||
action: "CreateRecord",
|
||||
version: "2024-09-10",
|
||||
method: "POST",
|
||||
data: {
|
||||
query: {
|
||||
SiteId: siteId,
|
||||
RecordName: fullRecord,
|
||||
Type: type,
|
||||
// queries["Ttl"] = 1231311;
|
||||
Ttl: 100,
|
||||
Data: JSON.stringify({ Value: value }),
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
this.logger.info('添加域名解析成功:', fullRecord, value, res.RecordId);
|
||||
return {
|
||||
RecordId: res.RecordId,
|
||||
SiteId: siteId,
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
async removeRecord(options: RemoveRecordOptions<any>): Promise<any> {
|
||||
const record = options.recordRes;
|
||||
|
||||
await this.client.doRequest({
|
||||
action: "DeleteRecord",
|
||||
version: "2024-09-10",
|
||||
data: {
|
||||
query: {
|
||||
RecordId: record.RecordId,
|
||||
}
|
||||
}
|
||||
})
|
||||
this.logger.info('删除域名解析成功:', record.RecordId);
|
||||
}
|
||||
}
|
||||
|
||||
new AliesaDnsProvider();
|
||||
@@ -1 +1,2 @@
|
||||
import './aliyun-dns-provider.js';
|
||||
import './aliesa-dns-provider.js';
|
||||
|
||||
@@ -6,7 +6,7 @@ import {
|
||||
createRemoteSelectInputDefine
|
||||
} from "@certd/plugin-lib";
|
||||
import { CertApplyPluginNames, CertInfo, CertReader } from "@certd/plugin-cert";
|
||||
import {optionsUtils} from "@certd/basic/dist/utils/util.options.js";
|
||||
import {optionsUtils} from "@certd/basic";
|
||||
|
||||
@IsTaskPlugin({
|
||||
name: 'DeployCertToAliyunApig',
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
import {AbstractTaskPlugin, IsTaskPlugin, pluginGroups, RunStrategy, TaskInput} from '@certd/pipeline';
|
||||
import {AliyunAccess, createCertDomainGetterInputDefine, createRemoteSelectInputDefine} from "@certd/plugin-lib";
|
||||
import {CertApplyPluginNames, CertInfo} from '@certd/plugin-cert';
|
||||
import {optionsUtils} from "@certd/basic/dist/utils/util.options.js";
|
||||
import {optionsUtils} from "@certd/basic";
|
||||
|
||||
@IsTaskPlugin({
|
||||
name: 'DeployCertToAliyunApiGateway',
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { AbstractTaskPlugin, IsTaskPlugin, pluginGroups, RunStrategy, TaskInput } from '@certd/pipeline';
|
||||
import { AliyunAccess, AliyunClient, AliyunSslClient, createCertDomainGetterInputDefine, createRemoteSelectInputDefine } from '@certd/plugin-lib';
|
||||
import { optionsUtils } from '@certd/basic/dist/utils/util.options.js';
|
||||
import { optionsUtils } from '@certd/basic';
|
||||
import { CertApplyPluginNames, CertReader } from "@certd/plugin-cert";
|
||||
@IsTaskPlugin({
|
||||
name: 'DeployCertToAliyunCDN',
|
||||
|
||||
@@ -8,7 +8,7 @@ import {
|
||||
} from "@certd/plugin-lib";
|
||||
import { CertInfo } from '@certd/plugin-cert';
|
||||
import { CertApplyPluginNames} from '@certd/plugin-cert';
|
||||
import { optionsUtils } from "@certd/basic/dist/utils/util.options.js";
|
||||
import { optionsUtils } from "@certd/basic";
|
||||
@IsTaskPlugin({
|
||||
name: 'DeployCertToAliyunDCDN',
|
||||
title: '阿里云-部署证书至DCDN',
|
||||
|
||||
@@ -7,7 +7,7 @@ import {
|
||||
} from '@certd/plugin-lib';
|
||||
import {CertInfo, CertReader} from '@certd/plugin-cert';
|
||||
import { CertApplyPluginNames} from '@certd/plugin-cert';
|
||||
import {optionsUtils} from "@certd/basic/dist/utils/util.options.js";
|
||||
import {optionsUtils} from "@certd/basic";
|
||||
import {isArray} from "lodash-es";
|
||||
@IsTaskPlugin({
|
||||
name: 'DeployCertToAliyunOSS',
|
||||
|
||||
+1
-1
@@ -3,7 +3,7 @@ import { CertApplyPluginNames, CertInfo } from "@certd/plugin-cert";
|
||||
import { AwsAccess, AwsRegions } from "../access.js";
|
||||
import { AwsAcmClient } from "../libs/aws-acm-client.js";
|
||||
import { createCertDomainGetterInputDefine, createRemoteSelectInputDefine } from "@certd/plugin-lib";
|
||||
import { optionsUtils } from "@certd/basic/dist/utils/util.options.js";
|
||||
import { optionsUtils } from "@certd/basic";
|
||||
|
||||
@IsTaskPlugin({
|
||||
name: 'AwsDeployToCloudFront',
|
||||
|
||||
@@ -1,2 +1,3 @@
|
||||
export * from './geetest/index.js';
|
||||
export * from './image/index.js';
|
||||
export * from './tencent/index.js';
|
||||
|
||||
@@ -0,0 +1,104 @@
|
||||
import { AddonInput, BaseAddon, IsAddon } from "@certd/lib-server";
|
||||
import { ICaptchaAddon } from "../api.js";
|
||||
import { TencentAccess } from "@certd/plugin-lib";
|
||||
|
||||
@IsAddon({
|
||||
addonType:"captcha",
|
||||
name: 'tencent',
|
||||
title: '腾讯云验证码',
|
||||
desc: '',
|
||||
showTest:false,
|
||||
})
|
||||
export class TencentCaptcha extends BaseAddon implements ICaptchaAddon{
|
||||
|
||||
@AddonInput({
|
||||
title: '腾讯云授权',
|
||||
helper: '腾讯云授权',
|
||||
component: {
|
||||
name: 'access-selector',
|
||||
vModel:"modelValue",
|
||||
from: "sys",
|
||||
type: 'tencent', //固定授权类型
|
||||
},
|
||||
required: true,
|
||||
})
|
||||
accessId :number;
|
||||
|
||||
@AddonInput({
|
||||
title: '验证ID',
|
||||
component: {
|
||||
name:"a-input-number",
|
||||
placeholder: 'CaptchaAppId',
|
||||
},
|
||||
helper:"[腾讯云验证码](https://cloud.tencent.com/act/cps/redirect?redirect=37716&cps_key=b3ef73330335d7a6efa4a4bbeeb6b2c9)",
|
||||
required: true,
|
||||
})
|
||||
captchaAppId:number;
|
||||
|
||||
@AddonInput({
|
||||
title: '验证Key',
|
||||
component: {
|
||||
placeholder: 'AppSecretKey',
|
||||
},
|
||||
required: true,
|
||||
})
|
||||
appSecretKey = '';
|
||||
|
||||
|
||||
async onValidate(data?:any) {
|
||||
if (!data) {
|
||||
return false
|
||||
}
|
||||
|
||||
|
||||
const access = await this.getAccess<TencentAccess>(this.accessId)
|
||||
|
||||
const sdk =await import("tencentcloud-sdk-nodejs/tencentcloud/services/captcha/v20190722/index.js");
|
||||
|
||||
const CaptchaClient = sdk.v20190722.Client;
|
||||
|
||||
const clientConfig = {
|
||||
credential: {
|
||||
secretId: access.secretId,
|
||||
secretKey: access.secretKey,
|
||||
},
|
||||
region: "",
|
||||
profile: {
|
||||
httpProfile: {
|
||||
endpoint: "captcha.tencentcloudapi.com",
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
// 实例化要请求产品的client对象,clientProfile是可选的
|
||||
const client = new CaptchaClient(clientConfig);
|
||||
const params = {
|
||||
"CaptchaType": 9, //固定值9
|
||||
"UserIp": "127.0.0.1",
|
||||
"Ticket": data.ticket,
|
||||
"Randstr": data.randstr,
|
||||
"AppSecretKey": this.appSecretKey,
|
||||
"CaptchaAppId": this.captchaAppId,
|
||||
};
|
||||
const res = await client.DescribeCaptchaResult(params)
|
||||
|
||||
if (res.CaptchaCode == 1) {
|
||||
// 验证成功
|
||||
// verification successful
|
||||
return true;
|
||||
} else {
|
||||
// 验证失败
|
||||
// verification failed
|
||||
this.logger.error("腾讯云验证码验证失败",res.CaptchaMsg)
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
async getCaptcha(): Promise<any> {
|
||||
return {
|
||||
captchaAppId: this.captchaAppId,
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
import { AbstractTaskPlugin, IsTaskPlugin, pluginGroups, RunStrategy, TaskInput } from '@certd/pipeline';
|
||||
import { CertInfo, CertReader } from '@certd/plugin-cert';
|
||||
import { createCertDomainGetterInputDefine, createRemoteSelectInputDefine } from '@certd/plugin-lib';
|
||||
import { optionsUtils } from '@certd/basic/dist/utils/util.options.js';
|
||||
import { optionsUtils } from '@certd/basic';
|
||||
import { CertApplyPluginNames} from '@certd/plugin-cert';
|
||||
@IsTaskPlugin({
|
||||
//命名规范,插件类型+功能(就是目录plugin-demo中的demo),大写字母开头,驼峰命名
|
||||
|
||||
@@ -6,7 +6,6 @@ import {
|
||||
SshAccess,
|
||||
SshClient
|
||||
} from "@certd/plugin-lib";
|
||||
import path from "node:path";
|
||||
|
||||
@IsTaskPlugin({
|
||||
//命名规范,插件类型+功能(就是目录plugin-demo中的demo),大写字母开头,驼峰命名
|
||||
@@ -57,7 +56,7 @@ export class FnOSDeployToNAS extends AbstractTaskPlugin {
|
||||
@TaskInput(
|
||||
createRemoteSelectInputDefine({
|
||||
title: "证书Id",
|
||||
helper: "要更新的证书id",
|
||||
helper: "面板证书请选择fnOS,其他FTP、webdav等证书请选择已使用,可多选(如果证书域名都匹配的话)",
|
||||
action: FnOSDeployToNAS.prototype.onGetCertList.name
|
||||
})
|
||||
)
|
||||
@@ -87,7 +86,9 @@ export class FnOSDeployToNAS extends AbstractTaskPlugin {
|
||||
this.logger.info(`----------- 找到证书,开始部署:${item.sum},${item.domain}`)
|
||||
const certPath = item.certificate;
|
||||
const keyPath = item.privateKey;
|
||||
const certDir = path.dirname(keyPath)
|
||||
const certDir = keyPath.substring(0, keyPath.lastIndexOf("/"));
|
||||
const fullchainPath = certDir+ "/fullchain.crt"
|
||||
const caPath = certDir+ "/issuer_certificate.crt"
|
||||
const cmd = `
|
||||
sudo tee ${certPath} > /dev/null <<'EOF'
|
||||
${this.cert.crt}
|
||||
@@ -95,6 +96,12 @@ EOF
|
||||
sudo tee ${keyPath} > /dev/null <<'EOF'
|
||||
${this.cert.key}
|
||||
EOF
|
||||
sudo tee ${fullchainPath} > /dev/null <<'EOF'
|
||||
${this.cert.crt}
|
||||
EOF
|
||||
sudo tee ${caPath} > /dev/null <<'EOF'
|
||||
${this.cert.ic}
|
||||
EOF
|
||||
|
||||
sudo chmod 0755 "${certDir}/" -R
|
||||
|
||||
@@ -157,7 +164,7 @@ echo "服务重启完成!"
|
||||
}
|
||||
|
||||
if (!list || list.length === 0) {
|
||||
throw new Error("没有找到证书,请先在证书管理也没上传一次证书");
|
||||
throw new Error("没有找到证书,请先在证书管理页面上传一次证书");
|
||||
}
|
||||
return list
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { AbstractTaskPlugin, IsTaskPlugin, pluginGroups, RunStrategy, TaskInput } from "@certd/pipeline";
|
||||
import { createCertDomainGetterInputDefine, createRemoteSelectInputDefine } from "@certd/plugin-lib";
|
||||
import { CertApplyPluginNames, CertInfo } from "@certd/plugin-cert";
|
||||
import { optionsUtils } from "@certd/basic/dist/utils/util.options.js";
|
||||
import { optionsUtils } from "@certd/basic";
|
||||
import { JDCloudAccess } from "../access.js";
|
||||
|
||||
@IsTaskPlugin({
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { AbstractTaskPlugin, IsTaskPlugin, pluginGroups, RunStrategy, TaskInput } from "@certd/pipeline";
|
||||
import { createCertDomainGetterInputDefine, createRemoteSelectInputDefine } from "@certd/plugin-lib";
|
||||
import { CertApplyPluginNames, CertInfo } from "@certd/plugin-cert";
|
||||
import { optionsUtils } from "@certd/basic/dist/utils/util.options.js";
|
||||
import { optionsUtils } from "@certd/basic";
|
||||
import { JDCloudAccess } from "../access.js";
|
||||
|
||||
@IsTaskPlugin({
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { AbstractTaskPlugin, IsTaskPlugin, pluginGroups, RunStrategy, TaskInput } from '@certd/pipeline';
|
||||
import { createCertDomainGetterInputDefine, createRemoteSelectInputDefine, QiniuAccess, QiniuClient } from '@certd/plugin-lib';
|
||||
import { CertInfo } from '@certd/plugin-cert';
|
||||
import { optionsUtils } from '@certd/basic/dist/utils/util.options.js';
|
||||
import { optionsUtils } from '@certd/basic';
|
||||
import { CertApplyPluginNames} from '@certd/plugin-cert';
|
||||
@IsTaskPlugin({
|
||||
name: 'QiniuDeployCertToCDN',
|
||||
|
||||
@@ -2,7 +2,7 @@ import { AbstractTaskPlugin, IsTaskPlugin, pluginGroups, RunStrategy, TaskInput
|
||||
import { CertApplyPluginNames, CertInfo } from "@certd/plugin-cert";
|
||||
import { UpyunAccess } from "../access.js";
|
||||
import { createCertDomainGetterInputDefine, createRemoteSelectInputDefine } from "@certd/plugin-lib";
|
||||
import { optionsUtils } from "@certd/basic/dist/utils/util.options.js";
|
||||
import { optionsUtils } from "@certd/basic";
|
||||
import { UpyunClient } from "../client.js";
|
||||
|
||||
@IsTaskPlugin({
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
import { AbstractTaskPlugin, IsTaskPlugin, pluginGroups, RunStrategy, TaskInput } from "@certd/pipeline";
|
||||
import { createCertDomainGetterInputDefine, createRemoteSelectInputDefine } from "@certd/plugin-lib";
|
||||
import { CertApplyPluginNames, CertInfo } from "@certd/plugin-cert";
|
||||
import { optionsUtils } from "@certd/basic/dist/utils/util.options.js";
|
||||
import { optionsUtils } from "@certd/basic";
|
||||
import { VolcengineAccess } from "../access.js";
|
||||
import { VolcengineCdnClient } from "../cdn-client.js";
|
||||
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
import { IsAccess, AccessInput, BaseAccess } from "@certd/pipeline";
|
||||
import { XinnetClient } from "@certd/plugin-plus";
|
||||
|
||||
/**
|
||||
* 这个注解将注册一个授权配置
|
||||
* 在certd的后台管理系统中,用户可以选择添加此类型的授权
|
||||
*/
|
||||
@IsAccess({
|
||||
name: "xinnet",
|
||||
title: "新网授权",
|
||||
icon: "lsicon:badge-new-filled",
|
||||
desc: ""
|
||||
})
|
||||
export class XinnetAccess extends BaseAccess {
|
||||
|
||||
/**
|
||||
* 授权属性配置
|
||||
*/
|
||||
@AccessInput({
|
||||
title: "用户名",
|
||||
component: {
|
||||
placeholder: "手机号/用户名"
|
||||
},
|
||||
required: true,
|
||||
encrypt: true
|
||||
})
|
||||
username = "";
|
||||
|
||||
@AccessInput({
|
||||
title: "登录密码",
|
||||
component: {
|
||||
name: "a-input-password",
|
||||
vModel: "value",
|
||||
placeholder: "登录密码"
|
||||
},
|
||||
required: true,
|
||||
encrypt: true
|
||||
})
|
||||
password = "";
|
||||
|
||||
@AccessInput({
|
||||
title: "测试",
|
||||
component: {
|
||||
name: "api-test",
|
||||
action: "TestRequest"
|
||||
},
|
||||
helper: "点击测试接口是否正常"
|
||||
})
|
||||
testRequest = true;
|
||||
|
||||
async onTestRequest() {
|
||||
|
||||
const client = new XinnetClient({
|
||||
access: this,
|
||||
logger: this.ctx.logger,
|
||||
http: this.ctx.http
|
||||
});
|
||||
|
||||
await client.getDomainList({ pageNo: 1, pageSize: 1 });
|
||||
|
||||
return "ok";
|
||||
}
|
||||
|
||||
|
||||
getCacheKey () {
|
||||
let hashStr = ""
|
||||
for (const key in this) {
|
||||
if (Object.prototype.hasOwnProperty.call(this, key)) {
|
||||
const element = this[key];
|
||||
hashStr += element;
|
||||
}
|
||||
}
|
||||
const hashCode = this.ctx.utils.hash.sha256(hashStr);
|
||||
return `xinnet-${hashCode}`;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
new XinnetAccess();
|
||||
@@ -0,0 +1,110 @@
|
||||
import { AbstractDnsProvider, CreateRecordOptions, IsDnsProvider, RemoveRecordOptions } from "@certd/plugin-cert";
|
||||
import { XinnetAccess } from "./access.js";
|
||||
import { XinnetClient } from "@certd/plugin-plus";
|
||||
|
||||
export type XinnetRecord = {
|
||||
recordId: number;
|
||||
recordFullName: string;
|
||||
recordValue: string;
|
||||
type: string;
|
||||
serviceCode: string;
|
||||
dcpCookie: string;
|
||||
};
|
||||
|
||||
// 这里通过IsDnsProvider注册一个dnsProvider
|
||||
@IsDnsProvider({
|
||||
name: "xinnet",
|
||||
title: "新网",
|
||||
desc: "新网域名解析",
|
||||
icon: "lsicon:badge-new-filled",
|
||||
// 这里是对应的 cloudflare的access类型名称
|
||||
accessType: "xinnet",
|
||||
order: 7
|
||||
})
|
||||
export class XinnetProvider extends AbstractDnsProvider<XinnetRecord> {
|
||||
access!: XinnetAccess;
|
||||
|
||||
async onInstance() {
|
||||
//一些初始化的操作
|
||||
// 也可以通过ctx成员变量传递context
|
||||
this.access = this.ctx.access as XinnetAccess;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建dns解析记录,用于验证域名所有权
|
||||
*/
|
||||
async createRecord(options: CreateRecordOptions): Promise<XinnetRecord> {
|
||||
/**
|
||||
* fullRecord: '_acme-challenge.test.example.com',
|
||||
* value: 一串uuid
|
||||
* type: 'TXT',
|
||||
* domain: 'example.com'
|
||||
*/
|
||||
const { fullRecord, hostRecord, value, type, domain } = options;
|
||||
this.logger.info("添加域名解析:", fullRecord, value, type, domain);
|
||||
|
||||
const client = new XinnetClient({
|
||||
logger: this.logger,
|
||||
access: this.access,
|
||||
http: this.http
|
||||
});
|
||||
|
||||
const res = await client.getDomainList({
|
||||
searchKey: domain
|
||||
});
|
||||
|
||||
if (!res.list || res.list.length == 0) {
|
||||
throw new Error("域名不存在");
|
||||
}
|
||||
const serviceCode = res.list[0].serviceCode;
|
||||
|
||||
const dcpCookie = await client.getDcpCookie({
|
||||
serviceCode
|
||||
});
|
||||
|
||||
const recordRes = await client.addDomainDnsRecord({
|
||||
recordName: hostRecord,
|
||||
type: type,
|
||||
recordValue: value
|
||||
}, {
|
||||
dcpCookie,
|
||||
serviceCode
|
||||
});
|
||||
return {
|
||||
...recordRes,
|
||||
serviceCode,
|
||||
dcpCookie
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 删除dns解析记录,清理申请痕迹
|
||||
* @param options
|
||||
*/
|
||||
async removeRecord(options: RemoveRecordOptions<XinnetRecord>): Promise<void> {
|
||||
const client = new XinnetClient({
|
||||
logger: this.logger,
|
||||
access: this.access,
|
||||
http: this.http
|
||||
});
|
||||
|
||||
const recordRes = options.recordRes;
|
||||
let dcpCookie = recordRes.dcpCookie;
|
||||
if (!dcpCookie) {
|
||||
dcpCookie = await client.getDcpCookie({
|
||||
serviceCode: recordRes.serviceCode
|
||||
});
|
||||
}
|
||||
|
||||
await client.deleteDomainDnsRecord(recordRes, {
|
||||
dcpCookie,
|
||||
serviceCode: recordRes.serviceCode
|
||||
});
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
//实例化这个provider,将其自动注册到系统中
|
||||
new XinnetProvider();
|
||||
@@ -0,0 +1,2 @@
|
||||
export * from './dns-provider.js';
|
||||
export * from './access.js';
|
||||
Reference in New Issue
Block a user