mirror of https://github.com/certd/certd
perf: 群晖支持OTP双重验证登录
parent
df55299e6f
commit
8b8039f42b
|
@ -0,0 +1,33 @@
|
||||||
|
import _ from "lodash-es";
|
||||||
|
import { HttpClient, ILogger } from "../utils";
|
||||||
|
|
||||||
|
export type PluginRequest = {
|
||||||
|
type: "plugin" | "access";
|
||||||
|
typeName: string;
|
||||||
|
action: string;
|
||||||
|
input: any;
|
||||||
|
data: any;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type RequestHandleContext = {
|
||||||
|
http: HttpClient;
|
||||||
|
logger: ILogger;
|
||||||
|
};
|
||||||
|
|
||||||
|
export class RequestHandler {
|
||||||
|
async onRequest(req: PluginRequest, ctx: RequestHandleContext) {
|
||||||
|
if (!req.action) {
|
||||||
|
throw new Error("action is required");
|
||||||
|
}
|
||||||
|
|
||||||
|
const methodName = `on${_.upperFirst(req.action)}`;
|
||||||
|
|
||||||
|
// @ts-ignore
|
||||||
|
const method = this[methodName];
|
||||||
|
if (method) {
|
||||||
|
// @ts-ignore
|
||||||
|
return await this[methodName](req.data, ctx);
|
||||||
|
}
|
||||||
|
throw new Error(`action ${req.action} not found`);
|
||||||
|
}
|
||||||
|
}
|
|
@ -4,3 +4,4 @@ export * from "./context.js";
|
||||||
export * from "./storage.js";
|
export * from "./storage.js";
|
||||||
export * from "./file-store.js";
|
export * from "./file-store.js";
|
||||||
export * from "./license.js";
|
export * from "./license.js";
|
||||||
|
export * from "./handler.js";
|
||||||
|
|
|
@ -45,7 +45,7 @@ export function createAxiosService({ logger }: { logger: Logger }) {
|
||||||
// 创建一个 axios 实例
|
// 创建一个 axios 实例
|
||||||
const service = axios.create();
|
const service = axios.create();
|
||||||
|
|
||||||
const defaultAgents = createAgent();
|
// const defaultAgents = createAgent();
|
||||||
// 请求拦截
|
// 请求拦截
|
||||||
service.interceptors.request.use(
|
service.interceptors.request.use(
|
||||||
(config: any) => {
|
(config: any) => {
|
||||||
|
@ -53,13 +53,14 @@ export function createAxiosService({ logger }: { logger: Logger }) {
|
||||||
if (config.timeout == null) {
|
if (config.timeout == null) {
|
||||||
config.timeout = 15000;
|
config.timeout = 15000;
|
||||||
}
|
}
|
||||||
let agents = defaultAgents;
|
// let agents = defaultAgents;
|
||||||
if (config.skipSslVerify) {
|
// if (config.skipSslVerify) {
|
||||||
agents = createAgent({ rejectUnauthorized: config.rejectUnauthorized });
|
// logger.info("跳过SSL验证");
|
||||||
}
|
// agents = createAgent({ rejectUnauthorized: config.rejectUnauthorized });
|
||||||
|
// }
|
||||||
config.httpsAgent = agents.httpsAgent;
|
// delete config.skipSslVerify;
|
||||||
config.httpAgent = agents.httpAgent;
|
// config.httpsAgent = agents.httpsAgent;
|
||||||
|
// config.httpAgent = agents.httpAgent;
|
||||||
|
|
||||||
return config;
|
return config;
|
||||||
},
|
},
|
||||||
|
|
|
@ -8,6 +8,7 @@ import { CheckCircleOutlined, InfoCircleOutlined, UndoOutlined } from "@ant-desi
|
||||||
import CronEditor from "./cron-editor/index.vue";
|
import CronEditor from "./cron-editor/index.vue";
|
||||||
import { CronLight } from "@vue-js-cron/light";
|
import { CronLight } from "@vue-js-cron/light";
|
||||||
import "@vue-js-cron/light/dist/light.css";
|
import "@vue-js-cron/light/dist/light.css";
|
||||||
|
import Plugins from "./plugins/index";
|
||||||
export default {
|
export default {
|
||||||
install(app: any) {
|
install(app: any) {
|
||||||
app.component("PiContainer", PiContainer);
|
app.component("PiContainer", PiContainer);
|
||||||
|
@ -24,5 +25,6 @@ export default {
|
||||||
app.component("UndoOutlined", UndoOutlined);
|
app.component("UndoOutlined", UndoOutlined);
|
||||||
|
|
||||||
app.use(vip);
|
app.use(vip);
|
||||||
|
app.use(Plugins);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -0,0 +1,6 @@
|
||||||
|
import PiSynologyIdDeviceGetter from "./synology/device-id-getter.vue";
|
||||||
|
export default {
|
||||||
|
install(app: any) {
|
||||||
|
app.component("PiSynologyDeviceIdGetter", PiSynologyIdDeviceGetter);
|
||||||
|
}
|
||||||
|
};
|
|
@ -0,0 +1,73 @@
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<contextHolder />
|
||||||
|
<a-input :value="value" :allow-clear="true" @update:value="emit('update:value', $event)">
|
||||||
|
<template #suffix>
|
||||||
|
<a-tag class="cursor-pointer" @click="getDeviceId">获取设备ID</a-tag>
|
||||||
|
</template>
|
||||||
|
</a-input>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="tsx" setup>
|
||||||
|
import { defineProps, ref, useAttrs } from "vue";
|
||||||
|
import { request } from "/@/api/service";
|
||||||
|
import { Modal } from "ant-design-vue";
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
type: string;
|
||||||
|
typeName: string;
|
||||||
|
form: any;
|
||||||
|
value?: any;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
"update:value": any;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const attrs = useAttrs();
|
||||||
|
|
||||||
|
const otpCodeRef = ref("");
|
||||||
|
|
||||||
|
async function doRequest(action: string, data: any) {
|
||||||
|
const res = await request({
|
||||||
|
url: "/pi/handle",
|
||||||
|
method: "post",
|
||||||
|
data: {
|
||||||
|
type: props.type,
|
||||||
|
typeName: props.typeName,
|
||||||
|
action,
|
||||||
|
data: data,
|
||||||
|
input: props.form.access
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function loginWithOTPCode(otpCode: string) {
|
||||||
|
return await doRequest("LoginWithOPTCode", {
|
||||||
|
otpCode
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const [modal, contextHolder] = Modal.useModal();
|
||||||
|
async function getDeviceId() {
|
||||||
|
//打开对话框
|
||||||
|
|
||||||
|
modal.confirm({
|
||||||
|
title: "请输入OTP验证码",
|
||||||
|
content: () => {
|
||||||
|
return (
|
||||||
|
<a-form-item-rest>
|
||||||
|
<a-input v-model:value={otpCodeRef.value} placeholder="请输入OTP验证码" />
|
||||||
|
</a-form-item-rest>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
onOk: async () => {
|
||||||
|
const res = await loginWithOTPCode(otpCodeRef.value);
|
||||||
|
console.log("did返回", res);
|
||||||
|
emit("update:value", res.did);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
</script>
|
|
@ -0,0 +1,36 @@
|
||||||
|
import { ALL, Body, Controller, Post, Provide } from '@midwayjs/core';
|
||||||
|
import { Constants } from '../../../basic/constants.js';
|
||||||
|
import { accessRegistry, http, logger, PluginRequest, RequestHandleContext } from '@certd/pipeline';
|
||||||
|
import { merge } from 'lodash-es';
|
||||||
|
import { BaseController } from '../../../basic/base-controller.js';
|
||||||
|
@Provide()
|
||||||
|
@Controller('/api/pi/handle')
|
||||||
|
export class HandleController extends BaseController {
|
||||||
|
@Post('/', { summary: Constants.per.authOnly })
|
||||||
|
async request(@Body(ALL) body: PluginRequest) {
|
||||||
|
const type = body.type;
|
||||||
|
if (type === 'access') {
|
||||||
|
const accessItem = accessRegistry.get(body.typeName);
|
||||||
|
const accessCls = accessItem.target;
|
||||||
|
if (accessCls == null) {
|
||||||
|
throw new Error(`access ${body.typeName} not found`);
|
||||||
|
}
|
||||||
|
//实例化access
|
||||||
|
//@ts-ignore
|
||||||
|
const access = new accessCls();
|
||||||
|
//注入input
|
||||||
|
merge(access, body.input);
|
||||||
|
const ctx: RequestHandleContext = {
|
||||||
|
http: http,
|
||||||
|
logger: logger,
|
||||||
|
};
|
||||||
|
const res = await access.onRequest(body, ctx);
|
||||||
|
|
||||||
|
return this.ok(res);
|
||||||
|
} else if (type === 'plugin') {
|
||||||
|
throw new Error(`plugin:${body.typeName} not support`);
|
||||||
|
} else {
|
||||||
|
throw new Error(`type:${type} not support`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue