mirror of https://github.com/certd/certd
perf: 登录支持极验验证码
parent
65f34f1d31
commit
370db62bf0
|
@ -1,8 +1,9 @@
|
|||
import { SysSettingsEntity } from './system/index.js';
|
||||
import { AccessEntity } from './user/access/entity/access.js';
|
||||
import { AddonEntity } from "./user/index.js";
|
||||
export * from './basic/index.js';
|
||||
export * from './system/index.js';
|
||||
export * from './user/index.js';
|
||||
export { LibServerConfiguration as Configuration } from './configuration.js';
|
||||
|
||||
export const libServerEntities = [SysSettingsEntity, AccessEntity];
|
||||
export const libServerEntities = [SysSettingsEntity, AccessEntity,AddonEntity];
|
||||
|
|
|
@ -36,6 +36,7 @@ export class SysPublicSettings extends BaseSettings {
|
|||
captchaEnabled = false;
|
||||
//验证码类型
|
||||
captchaType?: string;
|
||||
captchaAddonId?:string;
|
||||
}
|
||||
|
||||
export class SysPrivateSettings extends BaseSettings {
|
||||
|
@ -50,9 +51,6 @@ export class SysPrivateSettings extends BaseSettings {
|
|||
dnsResultOrder? = '';
|
||||
commonCnameEnabled?: boolean = true;
|
||||
|
||||
//验证码配置id
|
||||
captchaAddonId?: number;
|
||||
|
||||
sms?: {
|
||||
type?: string;
|
||||
config?: any;
|
||||
|
|
|
@ -12,7 +12,7 @@ export class AddonEntity {
|
|||
name: string;
|
||||
|
||||
|
||||
@Column({ comment: 'addon类型', length: 100 })
|
||||
@Column({ name: 'addon_type', comment: 'addon类型', length: 100 })
|
||||
addonType: string;
|
||||
|
||||
|
||||
|
|
|
@ -39,6 +39,11 @@ export class AddonService extends BaseService<AddonEntity> {
|
|||
throw new ValidateException('您无权查看该Addon配置');
|
||||
}
|
||||
}
|
||||
if (!param.userId){
|
||||
param.isSystem = true
|
||||
}else{
|
||||
param.isSystem = false
|
||||
}
|
||||
delete param._copyFrom
|
||||
return await super.add(param);
|
||||
}
|
||||
|
@ -65,6 +70,8 @@ export class AddonService extends BaseService<AddonEntity> {
|
|||
id: entity.id,
|
||||
name: entity.name,
|
||||
userId: entity.userId,
|
||||
addonType: entity.addonType,
|
||||
type: entity.type,
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -197,7 +204,7 @@ export class AddonService extends BaseService<AddonEntity> {
|
|||
|
||||
const addonDefine = this.getDefineByType( type,addonType)
|
||||
|
||||
const defaultConfig = await this.getDefault(userId);
|
||||
const defaultConfig = await this.getDefault(userId,addonType);
|
||||
if (defaultConfig) {
|
||||
return defaultConfig;
|
||||
}
|
||||
|
|
|
@ -711,6 +711,10 @@ export default {
|
|||
setting: {
|
||||
showRunStrategy: "Show RunStrategy",
|
||||
showRunStrategyHelper: "Allow modify the run strategy of the task",
|
||||
|
||||
captchaEnabled: "Enable Captcha",
|
||||
captchaHelper: "Whether to enable captcha verification for login",
|
||||
captchaType: "Captcha Type",
|
||||
},
|
||||
},
|
||||
modal: {
|
||||
|
@ -731,4 +735,8 @@ export default {
|
|||
challengeSetting: "Challenge Setting",
|
||||
gotoCnameTip: "Please go to CNAME Record Page",
|
||||
},
|
||||
addonSelector: {
|
||||
select: "Select",
|
||||
placeholder: "select please",
|
||||
},
|
||||
};
|
||||
|
|
|
@ -714,6 +714,10 @@ export default {
|
|||
setting: {
|
||||
showRunStrategy: "显示运行策略选择",
|
||||
showRunStrategyHelper: "任务设置中是否允许选择运行策略",
|
||||
|
||||
captchaEnabled: "启用验证码",
|
||||
captchaHelper: "登录时是否启用验证码",
|
||||
captchaType: "验证码类型",
|
||||
},
|
||||
},
|
||||
modal: {
|
||||
|
@ -734,4 +738,8 @@ export default {
|
|||
challengeSetting: "校验配置",
|
||||
gotoCnameTip: "CNAME域名配置请前往CNAME记录页面添加",
|
||||
},
|
||||
addonSelector: {
|
||||
select: "选择",
|
||||
placeholder: "请选择",
|
||||
},
|
||||
};
|
||||
|
|
|
@ -0,0 +1,173 @@
|
|||
<template>
|
||||
<div class="addon-selector">
|
||||
<div class="flex-o w-100">
|
||||
<!-- <fs-dict-select class="flex-1" :value="modelValue" :dict="optionsDictRef" :disabled="disabled" :render-label="renderLabel" :slots="selectSlots" :allow-clear="true" v-bind="select" @update:value="onChange" />-->
|
||||
<span v-if="modelValue" class="mr-5 cd-flex-inline">
|
||||
<a-tag class="mr-5" color="green">{{ target?.name || modelValue }}</a-tag>
|
||||
<fs-icon class="cd-icon-button" icon="ion:close-circle-outline" @click="clear"></fs-icon>
|
||||
</span>
|
||||
<span v-else class="mlr-5 text-gray">{{ placeholder || t("certd.addonSelector.placeholder") }}</span>
|
||||
<fs-table-select
|
||||
ref="tableSelectRef"
|
||||
class="flex-0"
|
||||
:model-value="modelValue"
|
||||
:dict="optionsDictRef"
|
||||
:create-crud-options="createCrudOptionsWithApi"
|
||||
:crud-options-override="{
|
||||
search: { show: false },
|
||||
table: {
|
||||
scroll: {
|
||||
x: 540,
|
||||
},
|
||||
},
|
||||
}"
|
||||
:show-current="false"
|
||||
:show-select="false"
|
||||
:dialog="{ width: 960 }"
|
||||
:destroy-on-close="false"
|
||||
height="400px"
|
||||
v-bind="tableSelect"
|
||||
@update:model-value="onChange"
|
||||
@dialog-closed="doRefresh"
|
||||
>
|
||||
<template #default="scope">
|
||||
<fs-button class="ml-5" :disabled="disabled" :size="size" type="primary" :text="t('certd.addonSelector.select')" @click="scope.open" />
|
||||
</template>
|
||||
</fs-table-select>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="tsx" setup>
|
||||
import { inject, ref, Ref, watch } from "vue";
|
||||
import { createAddonApi } from "../api";
|
||||
import { message } from "ant-design-vue";
|
||||
import { dict } from "@fast-crud/fast-crud";
|
||||
import createCrudOptions from "../crud";
|
||||
import { addonProvide } from "../common";
|
||||
import { useUserStore } from "/@/store/user";
|
||||
import { useI18n } from "/src/locales";
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
defineOptions({
|
||||
name: "AddonSelector",
|
||||
});
|
||||
|
||||
const props = defineProps<{
|
||||
modelValue?: number | string | number[] | string[];
|
||||
type?: string;
|
||||
placeholder?: string;
|
||||
size?: string;
|
||||
disabled?: boolean;
|
||||
select?: any;
|
||||
tableSelect?: any;
|
||||
addonType: string;
|
||||
from?: string;
|
||||
}>();
|
||||
|
||||
const onChange = async (value: number) => {
|
||||
await emitValue(value);
|
||||
};
|
||||
|
||||
const emit = defineEmits(["update:modelValue", "selected-change", "change"]);
|
||||
|
||||
const api = createAddonApi({
|
||||
from: props.from,
|
||||
addonType: props.addonType,
|
||||
});
|
||||
addonProvide(api);
|
||||
|
||||
function createCrudOptionsWithApi(opts: any) {
|
||||
opts.context = {
|
||||
api,
|
||||
addonType: props.addonType,
|
||||
};
|
||||
return createCrudOptions(opts);
|
||||
}
|
||||
|
||||
const tableSelectRef = ref();
|
||||
const optionsDictRef = dict({
|
||||
url: `/addon/options?addonType=${props.addonType}`,
|
||||
value: "id",
|
||||
label: "name",
|
||||
});
|
||||
|
||||
const renderLabel = (option: any) => {
|
||||
return <span>{option.name}</span>;
|
||||
};
|
||||
|
||||
async function openTableSelectDialog() {
|
||||
selectOpened.value = false;
|
||||
await tableSelectRef.value.open({});
|
||||
await tableSelectRef.value.crudExpose.openAdd({});
|
||||
}
|
||||
|
||||
const selectOpened = ref(false);
|
||||
const selectSlots = ref({
|
||||
dropdownRender({ menuNode, props }: any) {
|
||||
const res = [];
|
||||
res.push(menuNode);
|
||||
// res.push(<a-divider style="margin: 4px 0" />);
|
||||
// res.push(<a-space style="padding: 4px 8px" />);
|
||||
// res.push(<fs-button class="w-100" type="text" icon="plus-outlined" text="新建通知渠道" onClick={openTableSelectDialog}></fs-button>);
|
||||
return res;
|
||||
},
|
||||
});
|
||||
|
||||
const target: Ref<any> = ref({});
|
||||
|
||||
function clear() {
|
||||
if (props.disabled) {
|
||||
return;
|
||||
}
|
||||
emitValue(null);
|
||||
}
|
||||
|
||||
const userStore = useUserStore();
|
||||
|
||||
async function emitValue(value: any) {
|
||||
// target.value = optionsDictRef.dataMap[value];
|
||||
const userId = userStore.userInfo.id;
|
||||
if (pipeline?.value && pipeline.value.userId !== userId) {
|
||||
message.error(`对不起,您不能修改他人流水线的${props.addonType}设置`);
|
||||
return;
|
||||
}
|
||||
emit("change", value);
|
||||
emit("update:modelValue", value);
|
||||
}
|
||||
|
||||
async function refreshTarget(value: any) {
|
||||
if (value > 0) {
|
||||
target.value = await api.GetSimpleInfo(value);
|
||||
}
|
||||
}
|
||||
|
||||
watch(
|
||||
() => {
|
||||
return props.modelValue;
|
||||
},
|
||||
async value => {
|
||||
// await optionsDictRef.loadDict();
|
||||
//@ts-ignore
|
||||
await refreshTarget(value);
|
||||
// target.value = optionsDictRef.dataMap[value];
|
||||
emit("selected-change", target.value);
|
||||
},
|
||||
{
|
||||
immediate: true,
|
||||
}
|
||||
);
|
||||
|
||||
//当不在pipeline中编辑时,可能为空
|
||||
const pipeline = inject("pipeline", null);
|
||||
|
||||
async function doRefresh() {
|
||||
await optionsDictRef.reloadDict();
|
||||
}
|
||||
</script>
|
||||
<style lang="less">
|
||||
.addon-selector {
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
|
@ -1,14 +1,23 @@
|
|||
import { request } from "/src/api/service";
|
||||
import { RequestHandleReq } from "/@/components/plugins/lib";
|
||||
|
||||
export function createAddonApi() {
|
||||
const apiPrefix = "/addon";
|
||||
export function createAddonApi(opts: { from: any; addonType: string }) {
|
||||
let apiPrefix = "/addon";
|
||||
if (opts.from === "sys") {
|
||||
apiPrefix = "/sys/addon";
|
||||
}
|
||||
return {
|
||||
async GetList(query: any) {
|
||||
return await request({
|
||||
url: apiPrefix + "/page",
|
||||
method: "post",
|
||||
data: query,
|
||||
data: {
|
||||
...query,
|
||||
query: {
|
||||
addonType: opts.addonType,
|
||||
...query.query,
|
||||
},
|
||||
},
|
||||
});
|
||||
},
|
||||
|
||||
|
@ -16,7 +25,10 @@ export function createAddonApi() {
|
|||
return await request({
|
||||
url: apiPrefix + "/add",
|
||||
method: "post",
|
||||
data: obj,
|
||||
data: {
|
||||
...obj,
|
||||
addonType: opts.addonType,
|
||||
},
|
||||
});
|
||||
},
|
||||
|
||||
|
@ -46,7 +58,7 @@ export function createAddonApi() {
|
|||
|
||||
async GetOptions(id: number) {
|
||||
return await request({
|
||||
url: apiPrefix + "/options",
|
||||
url: apiPrefix + `/options?addonType=${opts.addonType}`,
|
||||
method: "post",
|
||||
});
|
||||
},
|
||||
|
@ -68,22 +80,22 @@ export function createAddonApi() {
|
|||
|
||||
async GetSimpleInfo(id: number) {
|
||||
return await request({
|
||||
url: apiPrefix + "/simpleInfo",
|
||||
url: apiPrefix + `/simpleInfo?addonType=${opts.addonType}`,
|
||||
method: "post",
|
||||
params: { id },
|
||||
});
|
||||
},
|
||||
|
||||
async GetDefineTypes(addonType: string) {
|
||||
async GetDefineTypes() {
|
||||
return await request({
|
||||
url: apiPrefix + `/getTypeDict?addonType=${addonType}`,
|
||||
url: apiPrefix + `/getTypeDict?addonType=${opts.addonType}`,
|
||||
method: "post",
|
||||
});
|
||||
},
|
||||
|
||||
async GetProviderDefine(type: string) {
|
||||
return await request({
|
||||
url: apiPrefix + "/define",
|
||||
url: apiPrefix + `/define?addonType=${opts.addonType}`,
|
||||
method: "post",
|
||||
params: { type },
|
||||
});
|
||||
|
@ -91,14 +103,14 @@ export function createAddonApi() {
|
|||
|
||||
async GetProviderDefineByType(type: string) {
|
||||
return await request({
|
||||
url: apiPrefix + "/defineByType",
|
||||
url: apiPrefix + `/defineByType?addonType=${opts.addonType}`,
|
||||
method: "post",
|
||||
params: { type },
|
||||
});
|
||||
},
|
||||
|
||||
async Handle(req: RequestHandleReq, opts: any = {}) {
|
||||
const url = `/pi/handle/${req.type}`;
|
||||
const url = `/handle/${req.type}?addonType=${opts.addonType}`;
|
||||
const { typeName, action, data, input } = req;
|
||||
const res = await request({
|
||||
url,
|
||||
|
|
|
@ -5,6 +5,7 @@ import { forEach, get, merge, set } from "lodash-es";
|
|||
import { Modal } from "ant-design-vue";
|
||||
import { mitter } from "/@/utils/util.mitt";
|
||||
import { useI18n } from "/src/locales";
|
||||
import * as pipelineApi from "/@/views/certd/pipeline/api";
|
||||
|
||||
export function addonProvide(api: any) {
|
||||
provide("addonApi", api);
|
||||
|
@ -13,13 +14,13 @@ export function addonProvide(api: any) {
|
|||
});
|
||||
}
|
||||
|
||||
export function getCommonColumnDefine(crudExpose: any, typeRef: any, api: any) {
|
||||
export function getCommonColumnDefine(crudExpose: any, typeRef: any, api: any, addonType: string) {
|
||||
const { t } = useI18n();
|
||||
const addonTypeTypeDictRef = dict({
|
||||
data: [{ value: "captcha", label: "验证码" }],
|
||||
});
|
||||
// const addonTypeTypeDictRef = dict({
|
||||
// data: [{ value: "captcha", label: "验证码" }],
|
||||
// });
|
||||
const addonTypeDictRef = dict({
|
||||
url: "/addon/getTypeDict?addonType=captcha",
|
||||
url: `/addon/getTypeDict?addonType=${addonType}`,
|
||||
});
|
||||
const defaultPluginConfig = {
|
||||
component: {
|
||||
|
@ -61,7 +62,6 @@ export function getCommonColumnDefine(crudExpose: any, typeRef: any, api: any) {
|
|||
}
|
||||
//字段配置赋值
|
||||
columnsRef.value[key] = column;
|
||||
console.log("form", columnsRef.value, form);
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -79,30 +79,30 @@ export function getCommonColumnDefine(crudExpose: any, typeRef: any, api: any) {
|
|||
show: false,
|
||||
},
|
||||
},
|
||||
addonType: {
|
||||
title: "Addon类型",
|
||||
type: "dict-select",
|
||||
dict: addonTypeTypeDictRef,
|
||||
search: {
|
||||
show: false,
|
||||
},
|
||||
column: {
|
||||
width: 200,
|
||||
component: {
|
||||
color: "auto",
|
||||
},
|
||||
},
|
||||
form: {
|
||||
onChange(ctx: { value: any }) {
|
||||
addonTypeDictRef.url = `/addon/getTypeDict?addonType=${ctx.value}`;
|
||||
},
|
||||
},
|
||||
editForm: {
|
||||
component: {
|
||||
disabled: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
// addonType: {
|
||||
// title: "Addon类型",
|
||||
// type: "dict-select",
|
||||
// dict: addonTypeTypeDictRef,
|
||||
// search: {
|
||||
// show: false,
|
||||
// },
|
||||
// column: {
|
||||
// width: 200,
|
||||
// component: {
|
||||
// color: "auto",
|
||||
// },
|
||||
// },
|
||||
// form: {
|
||||
// onChange(ctx: { value: any }) {
|
||||
// addonTypeDictRef.url = `/addon/getTypeDict?addonType=${ctx.value}`;
|
||||
// },
|
||||
// },
|
||||
// editForm: {
|
||||
// component: {
|
||||
// disabled: false,
|
||||
// },
|
||||
// },
|
||||
// },
|
||||
type: {
|
||||
title: t("certd.notificationType"),
|
||||
type: "dict-select",
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
import { ref } from "vue";
|
||||
import { getCommonColumnDefine } from "./common";
|
||||
import { AddReq, CreateCrudOptionsProps, CreateCrudOptionsRet, DelReq, EditReq, UserPageQuery, UserPageRes } from "@fast-crud/fast-crud";
|
||||
import { createAddonApi } from "/@/views/certd/addon/api";
|
||||
const api = createAddonApi();
|
||||
|
||||
export default function ({ crudExpose, context }: CreateCrudOptionsProps): CreateCrudOptionsRet {
|
||||
const api = context.api;
|
||||
const addonType = context.addonType;
|
||||
const pageRequest = async (query: UserPageQuery): Promise<UserPageRes> => {
|
||||
return await api.GetList(query);
|
||||
};
|
||||
|
@ -25,7 +26,7 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
|
|||
};
|
||||
|
||||
const typeRef = ref();
|
||||
const commonColumnsDefine = getCommonColumnDefine(crudExpose, typeRef, api);
|
||||
const commonColumnsDefine = getCommonColumnDefine(crudExpose, typeRef, api, addonType);
|
||||
return {
|
||||
crudOptions: {
|
||||
request: {
|
||||
|
|
|
@ -14,14 +14,14 @@
|
|||
import { defineComponent, onActivated, onMounted } from "vue";
|
||||
import { useFs } from "@fast-crud/fast-crud";
|
||||
import createCrudOptions from "./crud";
|
||||
import { createNotificationApi } from "./api";
|
||||
import { notificationProvide } from "/@/views/certd/notification/common";
|
||||
import { createAddonApi } from "./api";
|
||||
import { addonProvide } from "/@/views/certd/addon/common";
|
||||
|
||||
export default defineComponent({
|
||||
name: "NotificationManager",
|
||||
name: "AddonManager",
|
||||
setup() {
|
||||
const api = createNotificationApi();
|
||||
notificationProvide(api);
|
||||
const api = createAddonApi();
|
||||
addonProvide(api);
|
||||
const { crudBinding, crudRef, crudExpose } = useFs({ createCrudOptions, context: { api } });
|
||||
|
||||
// 页面打开后获取列表数据
|
||||
|
|
|
@ -0,0 +1,90 @@
|
|||
<template>
|
||||
<div ref="captchaRef" class="captcha_input"></div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { onMounted, defineProps, defineEmits, ref } from "vue";
|
||||
import { useSettingStore } from "/@/store/settings";
|
||||
import { request } from "/src/api/service";
|
||||
import { notification } from "ant-design-vue";
|
||||
const props = defineProps<{
|
||||
modelValue?: any;
|
||||
}>();
|
||||
const emit = defineEmits(["update:modelValue", "change"]);
|
||||
|
||||
const captchaRef = ref(null);
|
||||
// const addonApi = createAddonApi();
|
||||
const settingStore = useSettingStore();
|
||||
|
||||
const api = {
|
||||
async getClientParams(): Promise<any> {
|
||||
const res = await request({
|
||||
url: "/captcha/getParams",
|
||||
method: "post",
|
||||
});
|
||||
return res;
|
||||
},
|
||||
};
|
||||
|
||||
// async function getCaptchaAddonDefine() {
|
||||
// const type = settingStore.public.captchaType;
|
||||
// const define = addonApi.getDefineByType("captcha", type);
|
||||
// }
|
||||
|
||||
const captchaInstanceRef = ref({});
|
||||
async function init() {
|
||||
const params = await api.getClientParams();
|
||||
// @ts-ignore
|
||||
initGeetest4(
|
||||
{
|
||||
captchaId: params.captchaId,
|
||||
},
|
||||
(captcha: any) => {
|
||||
// captcha为验证码实例
|
||||
captcha.appendTo(captchaRef.value); // 调用appendTo将验证码插入到页的某一个元素中,这个元素用户可以自定义
|
||||
captchaInstanceRef.value.instance = captcha;
|
||||
captchaInstanceRef.value.captchaId = params.captchaId;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
async function getValidatedForm() {
|
||||
if (!captchaInstanceRef.value?.instance) {
|
||||
notification.error({
|
||||
message: "验证码还未初始化",
|
||||
});
|
||||
return false;
|
||||
}
|
||||
const result = await captchaInstanceRef.value.instance.getValidate();
|
||||
if (!result) {
|
||||
notification.error({
|
||||
message: "请先完成验证码验证",
|
||||
});
|
||||
return false;
|
||||
}
|
||||
result.captcha_id = captchaInstanceRef.value.captchaId;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
function onChange(value: string) {
|
||||
emit("update:modelValue", value);
|
||||
emit("change", value);
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
getValidatedForm,
|
||||
});
|
||||
|
||||
onMounted(async () => {
|
||||
await init();
|
||||
});
|
||||
</script>
|
||||
<style lang="less">
|
||||
.captcha_input {
|
||||
.geetest_captcha {
|
||||
.geetest_holder {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -1,46 +0,0 @@
|
|||
<template>
|
||||
<div class="captcha"></div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { doRequest } from "/@/components/plugins/lib";
|
||||
import { createAddonApi } from "/src/api/modules/addon";
|
||||
import { useSettingStore } from "/@/store/settings";
|
||||
const props = defineProps<{
|
||||
modelValue?: any;
|
||||
}>();
|
||||
const emit = defineEmits(["update:modelValue", "change"]);
|
||||
|
||||
const addonApi = createAddonApi();
|
||||
const settingStore = useSettingStore();
|
||||
async function getCaptchaAddonDefine() {
|
||||
const type = settingStore.public.captchaType;
|
||||
const define = addonApi.getDefineByType("captcha", type);
|
||||
|
||||
const res = await doRequest(
|
||||
{
|
||||
addonId: settingStore.public.captchaAddonId
|
||||
type: "captcha",
|
||||
typeName: type,
|
||||
action: "onGetParams",
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
function init() {
|
||||
// @ts-ignore
|
||||
initGeetest4(
|
||||
{
|
||||
captchaId: "您的captchaId",
|
||||
},
|
||||
(captcha: any) => {
|
||||
// captcha为验证码实例
|
||||
captcha.appendTo(".captcha"); // 调用appendTo将验证码插入到页的某一个元素中,这个元素用户可以自定义
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
function onChange(value: string) {
|
||||
emit("update:modelValue", value);
|
||||
emit("change", value);
|
||||
}
|
||||
</script>
|
|
@ -21,8 +21,8 @@
|
|||
</a-input-password>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item required name="captcha">
|
||||
<captcha v-model:model-value="formState.captcha"></captcha>
|
||||
<a-form-item v-if="settingStore.sysPublic.captchaEnabled" required name="captcha">
|
||||
<CaptchaInput ref="captchaInputRef" v-model:model-value="formState.captcha"></CaptchaInput>
|
||||
</a-form-item>
|
||||
</template>
|
||||
</a-tab-pane>
|
||||
|
@ -95,10 +95,10 @@ import ImageCode from "/@/views/framework/login/image-code.vue";
|
|||
import SmsCode from "/@/views/framework/login/sms-code.vue";
|
||||
import { useI18n } from "/@/locales";
|
||||
import { LanguageToggle } from "/@/vben/layouts";
|
||||
|
||||
import CaptchaInput from "./captcha-input.vue";
|
||||
export default defineComponent({
|
||||
name: "LoginPage",
|
||||
components: { LanguageToggle, SmsCode, ImageCode },
|
||||
components: { LanguageToggle, SmsCode, ImageCode, CaptchaInput },
|
||||
setup() {
|
||||
const { t } = useI18n();
|
||||
const verifyCodeInputRef = ref();
|
||||
|
@ -165,6 +165,10 @@ export default defineComponent({
|
|||
const handleFinish = async (values: any) => {
|
||||
loading.value = true;
|
||||
try {
|
||||
formState.captcha = await doCaptchaValidate();
|
||||
if (!formState.captcha) {
|
||||
return;
|
||||
}
|
||||
const loginType = formState.loginType;
|
||||
await userStore.login(loginType, toRaw(formState));
|
||||
} catch (e: any) {
|
||||
|
@ -199,6 +203,20 @@ export default defineComponent({
|
|||
return sysPublicSettings.registerEnabled && (sysPublicSettings.usernameRegisterEnabled || sysPublicSettings.emailRegisterEnabled);
|
||||
}
|
||||
|
||||
const captchaInputRef = ref();
|
||||
async function doCaptchaValidate() {
|
||||
if (!sysPublicSettings.captchaEnabled) {
|
||||
return {};
|
||||
}
|
||||
const res = await captchaInputRef.value.getValidatedForm();
|
||||
if (!res) {
|
||||
return false;
|
||||
}
|
||||
return {
|
||||
...res,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
t,
|
||||
loading,
|
||||
|
@ -216,6 +234,7 @@ export default defineComponent({
|
|||
handleTwoFactorSubmit,
|
||||
verifyCodeInputRef,
|
||||
settingStore,
|
||||
captchaInputRef,
|
||||
};
|
||||
},
|
||||
});
|
||||
|
|
|
@ -47,6 +47,20 @@
|
|||
<div class="helper" v-html="t('certd.commonCnameHelper')"></div>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item :label="t('certd.sys.setting.captchaEnabled')" :name="['public', 'captchaEnabled']">
|
||||
<a-switch v-model:checked="formState.public.captchaEnabled" />
|
||||
<div class="helper" v-html="t('certd.sys.setting.captchaHelper')"></div>
|
||||
</a-form-item>
|
||||
<a-form-item v-if="formState.public.captchaEnabled" :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-input v-model:model-value="formState.public.captchaType" class="hidden"></a-input>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item v-if="formState.public.captchaEnabled" :name="['public', 'captchaType']" class="hidden">
|
||||
<a-input v-model:model-value="formState.public.captchaType"></a-input>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item label=" " :colon="false" :wrapper-col="{ span: 8 }">
|
||||
<a-button :loading="saveLoading" type="primary" html-type="submit">{{ t("certd.saveButton") }}</a-button>
|
||||
</a-form-item>
|
||||
|
@ -63,7 +77,7 @@ import { useSettingStore } from "/@/store/settings";
|
|||
import { notification } from "ant-design-vue";
|
||||
import { util } from "/@/utils";
|
||||
import { useI18n } from "/src/locales";
|
||||
|
||||
import AddonSelector from "../../../certd/addon/addon-selector/index.vue";
|
||||
const { t } = useI18n();
|
||||
|
||||
defineOptions({
|
||||
|
@ -115,6 +129,11 @@ async function stopOtherUserTimer() {
|
|||
});
|
||||
}
|
||||
|
||||
function onAddonChanged(target: any) {
|
||||
debugger;
|
||||
formState.public.captchaType = target.type;
|
||||
}
|
||||
|
||||
const testProxyLoading = ref(false);
|
||||
async function testProxy() {
|
||||
testProxyLoading.value = true;
|
||||
|
|
|
@ -0,0 +1,13 @@
|
|||
|
||||
CREATE TABLE "cd_addon" (
|
||||
"id" integer PRIMARY KEY AUTOINCREMENT NOT NULL,
|
||||
"user_id" integer NOT NULL,
|
||||
"name" varchar(100) NOT NULL,
|
||||
"type" varchar(100) NOT NULL,
|
||||
"addon_type" varchar(100) NOT NULL,
|
||||
"is_default" boolean NOT NULL DEFAULT (false),
|
||||
"is_system" boolean NOT NULL DEFAULT (false),
|
||||
"setting" text,
|
||||
"create_time" datetime NOT NULL DEFAULT (CURRENT_TIMESTAMP),
|
||||
"update_time" datetime NOT NULL DEFAULT (CURRENT_TIMESTAMP)
|
||||
);
|
|
@ -0,0 +1,83 @@
|
|||
import { ALL, Body, Controller, Inject, Post, Provide, Query } from "@midwayjs/core";
|
||||
import { AddonRequestHandleReq, AddonService, Constants } from "@certd/lib-server";
|
||||
import { AddonController } from "../../user/addon/addon-controller.js";
|
||||
|
||||
@Provide()
|
||||
@Controller('/api/sys/addon')
|
||||
export class SysAddonController extends AddonController {
|
||||
@Inject()
|
||||
service2: AddonService;
|
||||
|
||||
getService(): AddonService {
|
||||
return this.service2;
|
||||
}
|
||||
|
||||
getUserId() {
|
||||
// checkComm();
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Post('/page', { summary: 'sys:settings:view' })
|
||||
async page(@Body(ALL) body: any) {
|
||||
return await super.page(body);
|
||||
}
|
||||
|
||||
@Post('/list', { summary: 'sys:settings:view' })
|
||||
async list(@Body(ALL) body: any) {
|
||||
return await super.list(body);
|
||||
}
|
||||
|
||||
@Post('/add', { summary: 'sys:settings:edit' })
|
||||
async add(@Body(ALL) bean: any) {
|
||||
return await super.add(bean);
|
||||
}
|
||||
|
||||
@Post('/update', { summary: 'sys:settings:edit' })
|
||||
async update(@Body(ALL) bean: any) {
|
||||
return await super.update(bean);
|
||||
}
|
||||
@Post('/info', { summary: 'sys:settings:view' })
|
||||
async info(@Query('id') id: number) {
|
||||
return await super.info(id);
|
||||
}
|
||||
|
||||
@Post('/delete', { summary: 'sys:settings:edit' })
|
||||
async delete(@Query('id') id: number) {
|
||||
return await super.delete(id);
|
||||
}
|
||||
@Post('/define', { summary: Constants.per.authOnly })
|
||||
async define(@Query('type') type: string,@Query('addonType') addonType: string) {
|
||||
return await super.define(type,addonType);
|
||||
}
|
||||
|
||||
@Post('/getTypeDict', { summary: Constants.per.authOnly })
|
||||
async getTypeDict(@Query('addonType') addonType: string) {
|
||||
return await super.getTypeDict(addonType);
|
||||
}
|
||||
|
||||
@Post('/simpleInfo', { summary: Constants.per.authOnly })
|
||||
async simpleInfo(@Query('addonType') addonType: string,@Query('id') id: number) {
|
||||
return await super.simpleInfo(addonType,id);
|
||||
}
|
||||
|
||||
@Post('/getDefaultId', { summary: Constants.per.authOnly })
|
||||
async getDefaultId(@Query('addonType') addonType: string) {
|
||||
return await super.getDefaultId(addonType);
|
||||
}
|
||||
|
||||
@Post('/setDefault', { summary: Constants.per.authOnly })
|
||||
async setDefault(@Query('addonType') addonType: string,@Query('id') id: number) {
|
||||
return await super.setDefault(addonType,id);
|
||||
}
|
||||
|
||||
|
||||
@Post('/options', { summary: Constants.per.authOnly })
|
||||
async options(@Query('addonType') addonType: string) {
|
||||
return await super.options(addonType);
|
||||
}
|
||||
|
||||
@Post('/handle', { summary: Constants.per.authOnly })
|
||||
async handle(@Body(ALL) body: AddonRequestHandleReq) {
|
||||
return await super.handle(body);
|
||||
}
|
||||
}
|
|
@ -1,18 +1,17 @@
|
|||
import { ALL, Body, Controller, Inject, Post, Provide, Query } from '@midwayjs/core';
|
||||
import { ALL, Body, Controller, Inject, Post, Provide, Query } from "@midwayjs/core";
|
||||
import {
|
||||
AccessGetter,
|
||||
AddonDefine,
|
||||
AddonRequestHandleReq,
|
||||
AddonService,
|
||||
Constants,
|
||||
CrudController,
|
||||
newAddon,
|
||||
ValidateException
|
||||
} from "@certd/lib-server";
|
||||
import { AuthService } from '../../../modules/sys/authority/service/auth-service.js';
|
||||
import { checkPlus } from '@certd/plus-core';
|
||||
import { AddonService } from "@certd/lib-server";
|
||||
import { AddonDefine } from "@certd/lib-server";
|
||||
import { AccessRequestHandleReq, newAccess } from "@certd/pipeline";
|
||||
import { AuthService } from "../../../modules/sys/authority/service/auth-service.js";
|
||||
import { checkPlus } from "@certd/plus-core";
|
||||
import { http, logger, utils } from "@certd/basic";
|
||||
|
||||
/**
|
||||
* Addon
|
||||
*/
|
||||
|
|
|
@ -1,8 +1,10 @@
|
|||
import { ALL, Body, Controller, Inject, Post, Provide } from '@midwayjs/core';
|
||||
import { LoginService } from '../../../modules/login/service/login-service.js';
|
||||
import { BaseController, Constants, SysPublicSettings, SysSettingsService } from '@certd/lib-server';
|
||||
import { CodeService } from '../../../modules/basic/service/code-service.js';
|
||||
import { checkComm } from '@certd/plus-core';
|
||||
import { ALL, Body, Controller, Inject, Post, Provide } from "@midwayjs/core";
|
||||
import { LoginService } from "../../../modules/login/service/login-service.js";
|
||||
import { AddonService, BaseController, Constants, SysPublicSettings, SysSettingsService } from "@certd/lib-server";
|
||||
import { CodeService } from "../../../modules/basic/service/code-service.js";
|
||||
import { checkComm } from "@certd/plus-core";
|
||||
import { logger } from "@certd/basic";
|
||||
import { ICaptchaAddon } from "../../../plugins/plugin-captcha/api.js";
|
||||
|
||||
/**
|
||||
*/
|
||||
|
@ -16,14 +18,16 @@ export class LoginController extends BaseController {
|
|||
|
||||
@Inject()
|
||||
sysSettingsService: SysSettingsService;
|
||||
@Inject()
|
||||
addonService: AddonService;
|
||||
|
||||
@Post('/login', { summary: Constants.per.guest })
|
||||
public async login(
|
||||
@Body(ALL)
|
||||
user: any
|
||||
body: any
|
||||
) {
|
||||
await this.loginService.doCaptchaValidate({form:user})
|
||||
const token = await this.loginService.loginByPassword(user);
|
||||
await this.loginService.doCaptchaValidate({form:body.captcha})
|
||||
const token = await this.loginService.loginByPassword(body);
|
||||
this.writeTokenCookie(token);
|
||||
return this.ok(token);
|
||||
}
|
||||
|
@ -79,4 +83,24 @@ export class LoginController extends BaseController {
|
|||
});
|
||||
return this.ok();
|
||||
}
|
||||
|
||||
@Post('/captcha/getParams', { summary: Constants.per.guest })
|
||||
async getCaptchaParams() {
|
||||
|
||||
const settings = await this.sysSettingsService.getPublicSettings()
|
||||
if (settings.captchaEnabled) {
|
||||
const addonId = settings.captchaAddonId;
|
||||
|
||||
const addon:ICaptchaAddon = await this.addonService.getAddonById(addonId,true,0)
|
||||
if (!addon) {
|
||||
logger.warn('验证码插件还未配置')
|
||||
return this.ok({});
|
||||
}
|
||||
|
||||
const params = await addon.getClientParams()
|
||||
return this.ok(params);
|
||||
}
|
||||
|
||||
return this.ok({});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -105,19 +105,21 @@ export class LoginService {
|
|||
const pubSetting = await this.sysSettingsService.getPublicSettings()
|
||||
|
||||
if (pubSetting.captchaEnabled) {
|
||||
const prvSetting = await this.sysSettingsService.getPrivateSettings()
|
||||
|
||||
const addon = await this.addonService.getById(prvSetting.captchaAddonId,0)
|
||||
const addon = await this.addonService.getById(pubSetting.captchaAddonId,0)
|
||||
if (!addon) {
|
||||
logger.warn('验证码插件还未配置,忽略验证码校验')
|
||||
return true
|
||||
}
|
||||
if (addon.addonType !== pubSetting.captchaType) {
|
||||
if (addon.define.name !== pubSetting.captchaType) {
|
||||
logger.warn('验证码插件类型错误,忽略验证码校验')
|
||||
return true
|
||||
}
|
||||
|
||||
return await addon.onValidate(opts.form)
|
||||
const res = await addon.onValidate(opts.form)
|
||||
if (!res) {
|
||||
throw new Error('验证码错误');
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
export interface ICaptchaAddon{
|
||||
onValidate(data?:any):Promise<any>;
|
||||
getClientParams():Promise<any>;
|
||||
}
|
|
@ -1,12 +1,13 @@
|
|||
import { AddonInput, BaseAddon, IsAddon } from "@certd/lib-server/dist/user/addon/api/index.js";
|
||||
import crypto from 'crypto';
|
||||
import { ICaptchaAddon } from "../api.js";
|
||||
@IsAddon({
|
||||
addonType:"captcha",
|
||||
name: 'geetest',
|
||||
title: '极验验证码',
|
||||
desc: '',
|
||||
})
|
||||
export class GeeTestCaptcha extends BaseAddon {
|
||||
export class GeeTestCaptcha extends BaseAddon implements ICaptchaAddon{
|
||||
@AddonInput({
|
||||
title: 'captchaId',
|
||||
component: {
|
||||
|
@ -43,6 +44,9 @@ export class GeeTestCaptcha extends BaseAddon {
|
|||
var captcha_output = data['captcha_output'];
|
||||
var pass_token = data['pass_token'];
|
||||
var gen_time = data['gen_time'];
|
||||
if (!lot_number || !captcha_output || !pass_token || !gen_time) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 生成签名, 使用标准的hmac算法,使用用户当前完成验证的流水号lot_number作为原始消息message,使用客户验证私钥作为key
|
||||
// 采用sha256散列算法将message和key进行单向散列生成最终的 “sign_token” 签名
|
||||
|
@ -78,8 +82,6 @@ export class GeeTestCaptcha extends BaseAddon {
|
|||
this.ctx.logger.error("极验验证服务异常",e)
|
||||
return true
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
// 生成签名
|
||||
|
@ -102,7 +104,13 @@ export class GeeTestCaptcha extends BaseAddon {
|
|||
timeout: 5000
|
||||
};
|
||||
const result = await this.ctx.http.request(options);
|
||||
return result.data;
|
||||
return result;
|
||||
}
|
||||
|
||||
async getClientParams(): Promise<any> {
|
||||
return {
|
||||
captchaId: this.captchaId,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
Loading…
Reference in New Issue