perf: 登录支持极验验证码

v2-dev-addon
xiaojunnuo 2025-09-11 23:47:05 +08:00
parent 65f34f1d31
commit 370db62bf0
22 changed files with 552 additions and 129 deletions

View File

@ -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];

View File

@ -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;

View File

@ -12,7 +12,7 @@ export class AddonEntity {
name: string;
@Column({ comment: 'addon类型', length: 100 })
@Column({ name: 'addon_type', comment: 'addon类型', length: 100 })
addonType: string;

View File

@ -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;
}

View File

@ -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",
},
};

View File

@ -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: "请选择",
},
};

View File

@ -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>

View File

@ -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,

View File

@ -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",

View File

@ -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: {

View File

@ -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 } });
//

View File

@ -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>

View File

@ -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>

View File

@ -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,
};
},
});

View File

@ -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;

View File

@ -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)
);

View File

@ -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);
}
}

View File

@ -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
*/

View File

@ -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({});
}
}

View File

@ -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

View File

@ -0,0 +1,4 @@
export interface ICaptchaAddon{
onValidate(data?:any):Promise<any>;
getClientParams():Promise<any>;
}

View File

@ -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,
}
}