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