mirror of https://github.com/certd/certd
perf: 验证码支持测试,登录验证码需要测试通过后才能开启
parent
03f317ffdb
commit
83e6476408
|
@ -24,5 +24,6 @@
|
|||
</div>
|
||||
<script type="module" src="/src/main.ts"></script>
|
||||
<script src="https://static.geetest.com/v4/gt4.js"></script>
|
||||
<script src="https://turing.captcha.qcloud.com/TJCaptcha.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
@ -0,0 +1,208 @@
|
|||
<template>
|
||||
<div ref="captchaRef" class="tencent_captcha_wrapper" :class="{ tencent_captcha_ok: modelValue }" @click="triggerCaptcha">
|
||||
<div class="validation-box" :class="{ validated: modelValue != null }">
|
||||
<div class="sweep-animation"></div>
|
||||
<div class="box-content">
|
||||
<div class="box-icon">✓</div>
|
||||
<span class="status-text">点击进行校验</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { onMounted, defineProps, defineEmits, ref, onUnmounted, Ref, watch } from "vue";
|
||||
import { notification } from "ant-design-vue";
|
||||
|
||||
defineOptions({
|
||||
name: "TencentCaptcha",
|
||||
});
|
||||
const emit = defineEmits(["update:modelValue", "change"]);
|
||||
const props = defineProps<{
|
||||
modelValue: any;
|
||||
captchaGet: () => Promise<any>;
|
||||
}>();
|
||||
const captchaRef = ref(null);
|
||||
|
||||
const captchaInstanceRef: Ref = ref({});
|
||||
|
||||
// 定义回调函数
|
||||
function callback(res: { ret: number; ticket: string; randstr: string; errorCode?: number; errorMessage?: string }) {
|
||||
// 第一个参数传入回调结果,结果如下:
|
||||
// ret Int 验证结果,0:验证成功。2:用户主动关闭验证码。
|
||||
// ticket String 验证成功的票据,当且仅当 ret = 0 时 ticket 有值。
|
||||
// CaptchaAppId String 验证码应用ID。
|
||||
// bizState Any 自定义透传参数。
|
||||
// randstr String 本次验证的随机串,后续票据校验时需传递该参数。
|
||||
// verifyDuration Int 验证码校验接口耗时(ms)。
|
||||
// actionDuration Int 操作校验成功耗时(用户动作+校验完成)(ms)。
|
||||
// sid String 链路sid。
|
||||
console.log("callback:", res);
|
||||
// res(用户主动关闭验证码)= {ret: 2, ticket: null}
|
||||
// res(验证成功) = {ret: 0, ticket: "String", randstr: "String"}
|
||||
// res(请求验证码发生错误,验证码自动返回trerror_前缀的容灾票据) = {ret: 0, ticket: "String", randstr: "String", errorCode: Number, errorMessage: "String"}
|
||||
// 此处代码仅为验证结果的展示示例,真实业务接入,建议基于ticket和errorCode情况做不同的业务处理
|
||||
if (res.ret === 0) {
|
||||
debugger;
|
||||
emitChange({
|
||||
ticket: res.ticket,
|
||||
randstr: res.randstr,
|
||||
});
|
||||
} else if (res.ret === 2) {
|
||||
console.log("用户主动关闭验证码");
|
||||
}
|
||||
}
|
||||
|
||||
// 定义验证码js加载错误处理函数
|
||||
function loadErrorCallback(error: any) {
|
||||
// var appid = "您的CaptchaAppId";
|
||||
// // 生成容灾票据或自行做其它处理
|
||||
// var ticket = "trerror_1001_" + appid + "_" + Math.floor(new Date().getTime() / 1000);
|
||||
// callback({
|
||||
// ret: 0,
|
||||
// randstr: "@" + Math.random().toString(36).substr(2),
|
||||
// ticket: ticket,
|
||||
// errorCode: 1001,
|
||||
// errorMessage: "jsload_error",
|
||||
// });
|
||||
notification.error({
|
||||
message: `验证码加载失败:${error?.message || error}`,
|
||||
});
|
||||
}
|
||||
async function triggerCaptcha() {
|
||||
const { captchaAppId } = await props.captchaGet();
|
||||
|
||||
try {
|
||||
// 生成一个验证码对象
|
||||
// CaptchaAppId:登录验证码控制台,从【验证管理】页面进行查看。如果未创建过验证,请先新建验证。注意:不可使用客户端类型为小程序的CaptchaAppId,会导致数据统计错误。
|
||||
//callback:定义的回调函数
|
||||
// @ts-ignore
|
||||
var captcha = new TencentCaptcha(captchaAppId + "", callback, {
|
||||
userLanguage: "zh-cn",
|
||||
// showFn: (ret: any) => {
|
||||
// const {
|
||||
// duration, // 验证码渲染完成的耗时(ms)
|
||||
// sid, // 链路sid
|
||||
// } = ret;
|
||||
// },
|
||||
});
|
||||
// 调用方法,显示验证码
|
||||
captcha.show();
|
||||
} catch (error) {
|
||||
// 加载异常,调用验证码js加载错误处理函数
|
||||
loadErrorCallback(error);
|
||||
}
|
||||
}
|
||||
|
||||
function emitChange(value: any) {
|
||||
emit("update:modelValue", value);
|
||||
emit("change", value);
|
||||
}
|
||||
function reset() {
|
||||
captchaInstanceRef.value.instance.reset();
|
||||
}
|
||||
|
||||
watch(
|
||||
() => {
|
||||
return props.modelValue;
|
||||
},
|
||||
value => {
|
||||
if (value == null) {
|
||||
reset();
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
defineExpose({
|
||||
reset,
|
||||
});
|
||||
</script>
|
||||
<style lang="less">
|
||||
.tencent_captcha_wrapper {
|
||||
.validation-box {
|
||||
width: 100%;
|
||||
height: 40px;
|
||||
margin: 0 auto 30px;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 8px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
background-color: #f9f9f9;
|
||||
}
|
||||
|
||||
.validation-box:hover {
|
||||
border-color: #aaa;
|
||||
background-color: #f0f0f0;
|
||||
}
|
||||
|
||||
.validation-box.validated {
|
||||
border-color: #4caf50;
|
||||
background-color: #f1f8e9;
|
||||
}
|
||||
|
||||
.box-content {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
z-index: 2;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.box-icon {
|
||||
font-size: 18px;
|
||||
color: #bbb;
|
||||
margin-right: 15px;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.validation-box.validated .box-icon {
|
||||
color: #4caf50;
|
||||
}
|
||||
|
||||
.status-text {
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
color: #888;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.validation-box.validated .status-text {
|
||||
color: #4caf50;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
/* 划过动画效果 */
|
||||
.sweep-animation {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: -100%;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: linear-gradient(90deg, transparent, rgba(76, 175, 80, 0.2), transparent);
|
||||
z-index: 1;
|
||||
opacity: 0;
|
||||
transition: opacity 0.3s;
|
||||
}
|
||||
|
||||
.validation-box.validated .sweep-animation {
|
||||
animation: sweep 0.8s ease forwards;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
@keyframes sweep {
|
||||
0% {
|
||||
left: -100%;
|
||||
}
|
||||
50% {
|
||||
left: 0;
|
||||
}
|
||||
100% {
|
||||
left: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -45,6 +45,16 @@ export async function DoVerify(id: number) {
|
|||
});
|
||||
}
|
||||
|
||||
export async function ResetStatus(id: number) {
|
||||
return await request({
|
||||
url: apiPrefix + "/resetStatus",
|
||||
method: "post",
|
||||
data: {
|
||||
id,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export async function ParseDomain(fullDomain: string) {
|
||||
return await request({
|
||||
url: subDomainApiPrefix + "/parseDomain",
|
||||
|
|
|
@ -16,6 +16,9 @@
|
|||
<a-tooltip v-if="cnameRecord.error" :title="cnameRecord.error">
|
||||
<fs-icon class="ml-5 color-red" icon="ion:warning-outline"></fs-icon>
|
||||
</a-tooltip>
|
||||
<a-tooltip v-if="cnameRecord.status === 'valid'" title="重置校验状态,重新校验">
|
||||
<fs-icon class="ml-5 color-red" icon="solar:undo-left-square-bold" @click="resetStatus"></fs-icon>
|
||||
</a-tooltip>
|
||||
</td>
|
||||
<td class="center">
|
||||
<template v-if="cnameRecord.status !== 'valid'">
|
||||
|
@ -71,12 +74,15 @@ function onRecordChange() {
|
|||
});
|
||||
}
|
||||
|
||||
async function loadRecord() {
|
||||
cnameRecord.value = await GetByDomain(props.domain);
|
||||
}
|
||||
let refreshIntervalId: any = null;
|
||||
async function doRefresh() {
|
||||
if (!props.domain) {
|
||||
return;
|
||||
}
|
||||
cnameRecord.value = await GetByDomain(props.domain);
|
||||
await loadRecord();
|
||||
onRecordChange();
|
||||
|
||||
if (cnameRecord.value.status === "validating") {
|
||||
|
@ -114,6 +120,11 @@ async function doVerify() {
|
|||
}
|
||||
await doRefresh();
|
||||
}
|
||||
|
||||
async function resetStatus() {
|
||||
await api.ResetStatus(cnameRecord.value.id);
|
||||
await loadRecord();
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less">
|
||||
|
|
|
@ -723,7 +723,9 @@ export default {
|
|||
captchaEnabled: "Enable Login Captcha",
|
||||
captchaHelper: "Whether to enable captcha verification for login",
|
||||
captchaType: "Captcha Setting",
|
||||
|
||||
captchaTest: "Captcha Test",
|
||||
// 保存后再点击测试,请务必测试通过了,再开启登录验证码
|
||||
captchaTestHelper: "Save and click test, please make sure the test is passed before enabling login captcha",
|
||||
baseSetting: "Base Settings",
|
||||
registerSetting: "Register Settings",
|
||||
safeSetting: "Safe Settings",
|
||||
|
|
|
@ -725,7 +725,8 @@ export default {
|
|||
captchaEnabled: "启用登录验证码",
|
||||
captchaHelper: "登录时是否启用验证码",
|
||||
captchaType: "验证码配置",
|
||||
|
||||
captchaTest: "测试验证码",
|
||||
captchaTestHelper: "保存后再点击测试,请务必测试通过了,再开启登录验证码",
|
||||
baseSetting: "基本设置",
|
||||
registerSetting: "注册设置",
|
||||
safeSetting: "安全设置",
|
||||
|
|
|
@ -79,6 +79,14 @@ export async function SysSettingsSave(data: SysSettings) {
|
|||
});
|
||||
}
|
||||
|
||||
export async function TestCaptcha(form: any) {
|
||||
return await request({
|
||||
url: apiPrefix + "/captchaTest",
|
||||
method: "post",
|
||||
data: form,
|
||||
});
|
||||
}
|
||||
|
||||
export async function TestProxy() {
|
||||
return await request({
|
||||
url: apiPrefix + "/testProxy",
|
||||
|
|
|
@ -58,7 +58,7 @@ function onChange(value: string) {
|
|||
<style lang="less">
|
||||
.page-sys-settings {
|
||||
.sys-settings-form {
|
||||
width: 600px;
|
||||
width: 800px;
|
||||
max-width: 100%;
|
||||
padding: 20px;
|
||||
}
|
||||
|
|
|
@ -54,6 +54,12 @@
|
|||
<a-form-item :label="t('certd.sys.setting.captchaType')" :name="['public', 'captchaAddonId']">
|
||||
<addon-selector v-model:model-value="formState.public.captchaAddonId" addon-type="captcha" from="sys" @selected-change="onAddonChanged" />
|
||||
</a-form-item>
|
||||
<a-form-item v-if="formState.public.captchaType" :label="t('certd.sys.setting.captchaTest')">
|
||||
<div class="flex">
|
||||
<CaptchaInput v-model:model-value="captchaTestForm.captcha" class="w-50%"></CaptchaInput>
|
||||
<a-button class="ml-2" type="primary" @click="doCaptchaValidate">后端验证</a-button>
|
||||
</div>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item :name="['public', 'captchaType']" class="hidden">
|
||||
<a-input v-model:model-value="formState.public.captchaType"></a-input>
|
||||
|
@ -76,12 +82,32 @@ import { notification } from "ant-design-vue";
|
|||
import { util } from "/@/utils";
|
||||
import { useI18n } from "/src/locales";
|
||||
import AddonSelector from "../../../certd/addon/addon-selector/index.vue";
|
||||
import CaptchaInput from "/@/components/captcha/captcha-input.vue";
|
||||
const { t } = useI18n();
|
||||
|
||||
defineOptions({
|
||||
name: "SettingBase",
|
||||
});
|
||||
|
||||
const captchaTestForm = reactive({
|
||||
captcha: null,
|
||||
pass: false,
|
||||
});
|
||||
|
||||
async function doCaptchaValidate() {
|
||||
if (!captchaTestForm.captcha) {
|
||||
notification.error({
|
||||
message: "请进行验证码验证",
|
||||
});
|
||||
return;
|
||||
}
|
||||
await api.TestCaptcha(captchaTestForm.captcha);
|
||||
notification.success({
|
||||
message: "校验通过",
|
||||
});
|
||||
captchaTestForm.pass = true;
|
||||
}
|
||||
|
||||
const formState = reactive<Partial<SysSettings>>({
|
||||
public: {
|
||||
icpNo: "",
|
||||
|
@ -106,6 +132,14 @@ const settingsStore = useSettingStore();
|
|||
const onFinish = async (form: any) => {
|
||||
try {
|
||||
saveLoading.value = true;
|
||||
|
||||
if (form.public.captchaEnabled && !captchaTestForm.pass) {
|
||||
notification.error({
|
||||
message: "请先通过验证码测试之后再开启登录验证码",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
await api.SysSettingsSave(form);
|
||||
await settingsStore.loadSysSettings();
|
||||
notification.success({
|
||||
|
@ -113,6 +147,7 @@ const onFinish = async (form: any) => {
|
|||
});
|
||||
} finally {
|
||||
saveLoading.value = false;
|
||||
captchaTestForm.pass = false;
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -115,6 +115,7 @@ export class CnameRecordService extends BaseService<CnameRecordEntity> {
|
|||
hostRecord = hostRecord.substring(0, hostRecord.length - 1);
|
||||
}
|
||||
param.hostRecord = hostRecord;
|
||||
param.mainDomain = realDomain;
|
||||
|
||||
const randomKey = utils.id.simpleNanoId(6).toLowerCase();
|
||||
|
||||
|
@ -191,6 +192,19 @@ export class CnameRecordService extends BaseService<CnameRecordEntity> {
|
|||
throw new ValidateException(`找不到${domain}的CNAME记录`);
|
||||
}
|
||||
}
|
||||
|
||||
if (!record.mainDomain){
|
||||
let domainPrefix = record.hostRecord.replace("_acme-challenge", "");
|
||||
if (domainPrefix.startsWith(".")) {
|
||||
domainPrefix = domainPrefix.substring(1);
|
||||
}
|
||||
record.mainDomain = record.domain.replace(domainPrefix, "");
|
||||
await this.update({
|
||||
id: record.id,
|
||||
mainDomain: domainPrefix,
|
||||
})
|
||||
}
|
||||
|
||||
const provider = await this.cnameProviderService.info(record.cnameProviderId);
|
||||
if (provider == null) {
|
||||
throw new ValidateException(`找不到${domain}的CNAME服务`);
|
||||
|
@ -208,7 +222,7 @@ export class CnameRecordService extends BaseService<CnameRecordEntity> {
|
|||
* 验证是否配置好cname
|
||||
* @param id
|
||||
*/
|
||||
async verify(id: string) {
|
||||
async verify(id: number) {
|
||||
const bean = await this.info(id);
|
||||
if (!bean) {
|
||||
throw new ValidateException(`CnameRecord:${id} 不存在`);
|
||||
|
@ -440,4 +454,11 @@ export class CnameRecordService extends BaseService<CnameRecordEntity> {
|
|||
}
|
||||
|
||||
}
|
||||
|
||||
async resetStatus (id: number) {
|
||||
if (!id) {
|
||||
throw new ValidateException('id不能为空');
|
||||
}
|
||||
await this.getRepository().update(id, {status: 'cname',mainDomain: ""});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -68,10 +68,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 {
|
||||
|
@ -84,6 +80,10 @@ export class TaskServiceBuilder {
|
|||
}
|
||||
}
|
||||
|
||||
export type TaskServiceCreateReq = {
|
||||
userId: number;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in New Issue