perf: 群晖支持OTP双重验证登录

pull/189/head
xiaojunnuo 2024-09-20 19:29:16 +08:00
parent df55299e6f
commit 8b8039f42b
7 changed files with 160 additions and 8 deletions

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,6 @@
import PiSynologyIdDeviceGetter from "./synology/device-id-getter.vue";
export default {
install(app: any) {
app.component("PiSynologyDeviceIdGetter", PiSynologyIdDeviceGetter);
}
};

View File

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

View File

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