Merge branch 'v2-dev' into v2-dev-buy

# Conflicts:
#	packages/core/basic/src/utils/util.hash.ts
This commit is contained in:
xiaojunnuo
2025-09-24 01:58:11 +08:00
56 changed files with 869 additions and 246 deletions
+10
View File
@@ -3,6 +3,16 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.36.22](https://github.com/certd/certd/compare/v1.36.21...v1.36.22) (2025-09-23)
### Bug Fixes
* 选择授权对话框编辑时,名称字段排在最后的bug ([31cfb09](https://github.com/certd/certd/commit/31cfb09468bda3272f5f63af65ff3e9272220b39))
### Performance Improvements
* 登录失败时清除验证码状态 ([1c15bea](https://github.com/certd/certd/commit/1c15beadc7fe8a7c6ec1903b7e722ca2f52e05b3))
## [1.36.21](https://github.com/certd/certd/compare/v1.36.20...v1.36.21) (2025-09-15)
### Bug Fixes
+3 -3
View File
@@ -1,6 +1,6 @@
{
"name": "@certd/ui-client",
"version": "1.36.21",
"version": "1.36.22",
"private": true,
"scripts": {
"dev": "vite --open",
@@ -104,8 +104,8 @@
"zod-defaults": "^0.1.3"
},
"devDependencies": {
"@certd/lib-iframe": "^1.36.21",
"@certd/pipeline": "^1.36.21",
"@certd/lib-iframe": "^1.36.22",
"@certd/pipeline": "^1.36.22",
"@rollup/plugin-commonjs": "^25.0.7",
"@rollup/plugin-node-resolve": "^15.2.3",
"@types/chai": "^4.3.12",
@@ -1,5 +1,5 @@
<template>
<component :is="captchaComponent" v-if="settingStore.inited" ref="captchaRef" class="captcha_input" :captcha-get="getCaptcha" @change="onChange" />
<component :is="captchaComponent" v-if="settingStore.inited" ref="captchaRef" :model-value="modelValue" class="captcha_input" :captcha-get="getCaptcha" @change="onChange" />
</template>
<script setup lang="ts">
import { ref, computed, defineAsyncComponent } from "vue";
@@ -7,6 +7,12 @@ import { useSettingStore } from "/@/store/settings";
import { nanoid } from "nanoid";
import { request } from "/@/api/service";
const props = defineProps({
modelValue: {
type: Object,
default: () => ({}),
},
});
const captchaRef = ref(null);
const settingStore = useSettingStore();
@@ -17,7 +23,7 @@ const captchaAddonId = computed(() => {
return settingStore.sysPublic.captchaAddonId ?? 0;
});
const captchaComponent = computed(() => {
let type = "image";
let type: any = "image";
if (settingStore.sysPublic.captchaAddonId && settingStore.sysPublic.captchaType) {
type = settingStore.sysPublic.captchaType;
}
@@ -36,7 +42,7 @@ async function getCaptcha(): Promise<any> {
});
}
function onChange(data) {
function onChange(data: any) {
emits("update:modelValue", data);
emits("change", data);
}
@@ -44,7 +50,11 @@ function onChange(data) {
async function getCaptchaForm() {
return await captchaRef.value.getCaptchaForm();
}
async function reset() {
await captchaRef.value.reset();
}
defineExpose({
getCaptchaForm,
reset,
});
</script>
@@ -2,7 +2,7 @@
<div ref="captchaRef" class="geetest_captcha_wrapper"></div>
</template>
<script setup lang="ts">
import { onMounted, defineProps, defineEmits, ref, onUnmounted } from "vue";
import { onMounted, defineProps, defineEmits, ref, onUnmounted, Ref, watch } from "vue";
import { useSettingStore } from "/@/store/settings";
import { request } from "/src/api/service";
import { notification } from "ant-design-vue";
@@ -12,13 +12,14 @@ defineOptions({
});
const emit = defineEmits(["update:modelValue", "change"]);
const props = defineProps<{
modelValue: any;
captchaGet: () => Promise<any>;
}>();
const captchaRef = ref(null);
// const addonApi = createAddonApi();
const settingStore = useSettingStore();
const captchaInstanceRef = ref({});
const captchaInstanceRef: Ref = ref({});
async function init() {
// if (!initGeetest4) {
// await import("https://static.geetest.com/v4/gt4.js");
@@ -35,6 +36,13 @@ async function init() {
captcha.appendTo(captchaRef.value); // 调用appendTo将验证码插入到页的某一个元素中,这个元素用户可以自定义
captchaInstanceRef.value.instance = captcha;
captchaInstanceRef.value.captchaId = captchaId;
captcha.onSuccess(function () {
const form = getCaptchaForm();
if (form) {
emitChange(form);
}
});
}
);
}
@@ -58,29 +66,51 @@ function getCaptchaForm() {
return result;
}
const valueRef = ref(null);
const timeoutId = setInterval(() => {
const form = getCaptchaForm();
if (form && valueRef.value != form) {
console.log("form", form);
valueRef.value = form;
emitChange(form);
}
}, 1000);
// const valueRef = ref(null);
// const timeoutId = setInterval(() => {
// const form = getCaptchaForm();
// if (form && valueRef.value != form) {
// console.log("form", form);
// valueRef.value = form;
// emitChange(form);
// }
// }, 1000);
onUnmounted(() => {
clearTimeout(timeoutId);
});
// onUnmounted(() => {
// clearTimeout(timeoutId);
// });
function emitChange(value: string) {
emit("update:modelValue", value);
emit("change", value);
}
function reset() {
captchaInstanceRef.value.instance.reset();
}
watch(
() => {
return props.modelValue;
},
value => {
if (value == null) {
reset();
}
}
);
defineExpose({
getCaptchaForm,
reset,
});
watch(
() => [props.captchaGet],
async () => {
await init();
}
);
onMounted(async () => {
await init();
});
@@ -11,10 +11,11 @@
</div>
</template>
<script setup lang="ts">
import { defineEmits, defineExpose, defineProps, ref } from "vue";
import { defineEmits, defineExpose, defineProps, ref, watch } from "vue";
import { nanoid } from "nanoid";
const props = defineProps<{
modelValue: any;
captchaGet?: () => Promise<any>;
}>();
defineOptions({
@@ -42,6 +43,7 @@ function getCaptchaForm() {
defineExpose({
resetImageCode,
getCaptchaForm,
reset: resetImageCode,
});
resetImageCode();
@@ -52,7 +54,18 @@ function onChange(value: string) {
emitChange(form);
}
function emitChange(value) {
watch(
() => {
return props.modelValue;
},
value => {
if (value == null) {
resetImageCode();
}
}
);
function emitChange(value: any) {
emit("update:modelValue", value);
emit("change", value);
}
@@ -21,10 +21,17 @@
</a-input>
</a-form-item>
<a-form-item has-feedback name="captchaForEmail" label="验证码">
<CaptchaInput v-model:model-value="formState.captchaForEmail"></CaptchaInput>
<CaptchaInput ref="captchaForEmailRef" v-model:model-value="formState.captchaForEmail"></CaptchaInput>
</a-form-item>
<a-form-item has-feedback name="validateCode" label="邮件验证码">
<email-code v-model:value="formState.validateCode" :captcha="formState.captchaForEmail" :email="formState.input" :random-str="formState.randomStr" verification-type="forgotPassword" />
<email-code
v-model:value="formState.validateCode"
:captcha="formState.captchaForEmail"
:email="formState.input"
:random-str="formState.randomStr"
verification-type="forgotPassword"
@error="formState.captchaForEmail = null"
/>
</a-form-item>
</a-tab-pane>
<a-tab-pane key="mobile" tab="手机号找回">
@@ -36,10 +43,17 @@
</a-input>
</a-form-item>
<a-form-item has-feedback name="captchaForSms" label="验证码">
<CaptchaInput v-model:model-value="formState.captchaForSms"></CaptchaInput>
<CaptchaInput ref="captchaForSmsRef" v-model:model-value="formState.captchaForSms"></CaptchaInput>
</a-form-item>
<a-form-item name="validateCode" label="手机验证码">
<sms-code v-model:value="formState.validateCode" :captcha="formState.captchaForSms" :mobile="formState.input" :phone-code="formState.phoneCode" verification-type="forgotPassword" />
<sms-code
v-model:value="formState.validateCode"
:captcha="formState.captchaForSms"
:mobile="formState.input"
:phone-code="formState.phoneCode"
verification-type="forgotPassword"
@error="formState.captchaForSms = null"
/>
</a-form-item>
</a-tab-pane>
</a-tabs>
@@ -41,7 +41,7 @@
</a-form-item>
<a-form-item name="smsCode" :rules="rules.smsCode">
<sms-code v-model:value="formState.smsCode" :captcha="formState.smsCaptcha" :mobile="formState.mobile" :phone-code="formState.phoneCode" />
<sms-code v-model:value="formState.smsCode" :captcha="formState.smsCaptcha" :mobile="formState.mobile" :phone-code="formState.phoneCode" @error="formState.smsCaptcha = null" />
</a-form-item>
</template>
</a-tab-pane>
@@ -188,6 +188,7 @@ export default defineComponent({
}
} finally {
loading.value = false;
formState.captcha = null;
}
};
@@ -209,18 +210,6 @@ export default defineComponent({
const captchaInputRef = ref();
const captchaInputForSmsCode = ref();
async function doCaptchaValidate() {
if (!sysPublicSettings.captchaEnabled) {
return {};
}
const res = await captchaInputRef.value.getValidatedForm();
if (!res) {
return false;
}
return {
...res,
};
}
return {
t,
@@ -24,7 +24,7 @@ const props = defineProps<{
captcha?: any;
verificationType?: string;
}>();
const emit = defineEmits(["update:value", "change"]);
const emit = defineEmits(["update:value", "change", "error"]);
function onChange(value: string) {
emit("update:value", value);
@@ -59,6 +59,9 @@ async function sendSmsCode() {
captcha: props.captcha,
verificationType: props.verificationType,
});
} catch (e) {
emit("error", e);
throw e;
} finally {
loading.value = false;
}
@@ -23,7 +23,7 @@ const props = defineProps<{
captcha?: any;
verificationType?: string;
}>();
const emit = defineEmits(["update:value", "change"]);
const emit = defineEmits(["update:value", "change", "error"]);
function onChange(value: string) {
emit("update:value", value);
@@ -54,6 +54,9 @@ async function sendSmsCode() {
captcha: props.captcha,
verificationType: props.verificationType,
});
} catch (e) {
emit("error", e);
throw e;
} finally {
loading.value = false;
}
@@ -66,7 +66,7 @@
</a-form-item>
<a-form-item has-feedback name="validateCode" :rules="rules.validateCode" label="邮件验证码">
<email-code v-model:value="formState.validateCode" :captcha="formState.captchaForEmail" :email="formState.email" />
<email-code v-model:value="formState.validateCode" :captcha="formState.captchaForEmail" :email="formState.email" @error="formState.captchaForEmail = null" />
</a-form-item>
</template>
</a-tab-pane>
@@ -182,16 +182,20 @@ export default defineComponent({
};
const handleFinish = async (values: any) => {
await userStore.register(
toRaw({
type: registerType.value,
password: formState.password,
username: formState.username,
email: formState.email,
captcha: formState.captcha,
validateCode: formState.validateCode,
}) as any
);
try {
await userStore.register(
toRaw({
type: registerType.value,
password: formState.password,
username: formState.username,
email: formState.email,
captcha: formState.captcha,
validateCode: formState.validateCode,
}) as any
);
} finally {
formState.captcha = null;
}
};
const handleFinishFailed = (errors: any) => {
+9
View File
@@ -3,6 +3,15 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [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
+14 -14
View File
@@ -1,6 +1,6 @@
{
"name": "@certd/ui-server",
"version": "1.36.21",
"version": "1.36.22",
"description": "fast-server base midway",
"private": true,
"type": "module",
@@ -43,20 +43,20 @@
"@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.36.22",
"@certd/basic": "^1.36.22",
"@certd/commercial-core": "^1.36.22",
"@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.36.22",
"@certd/lib-huawei": "^1.36.22",
"@certd/lib-k8s": "^1.36.22",
"@certd/lib-server": "^1.36.22",
"@certd/midway-flyway-js": "^1.36.22",
"@certd/pipeline": "^1.36.22",
"@certd/plugin-cert": "^1.36.22",
"@certd/plugin-lib": "^1.36.22",
"@certd/plugin-plus": "^1.36.22",
"@certd/plus-core": "^1.36.22",
"@huaweicloud/huaweicloud-sdk-cdn": "^3.1.120",
"@huaweicloud/huaweicloud-sdk-core": "^3.1.120",
"@koa/cors": "^5.0.0",
@@ -21,6 +21,7 @@ import { DomainParser } from "@certd/plugin-cert/dist/dns-provider/domain-parser
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;
@@ -55,6 +56,10 @@ export class CnameRecordService extends BaseService<CnameRecordEntity> {
@Inject()
subDomainService: SubDomainService;
@Inject()
taskServiceBuilder: TaskServiceBuilder;
//@ts-ignore
getRepository() {
return this.repository;
@@ -250,8 +255,9 @@ export class CnameRecordService extends BaseService<CnameRecordEntity> {
});
}
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,
@@ -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;
@@ -29,8 +32,14 @@ export class TaskServiceGetter implements IServiceGetter{
return await this.getNotificationService() as T
} else if (serviceName === 'domainVerifierGetter') {
return await this.getDomainVerifierGetter() as T
}else{
throw new Error(`service ${serviceName} not found`)
} else{
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`)
}
}
}
@@ -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'
@@ -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';