From 6eedd0a237fdaa0e4ccc4835e5fc6c4b67520158 Mon Sep 17 00:00:00 2001 From: bill Date: Mon, 15 Dec 2025 16:38:03 +0800 Subject: [PATCH] Feat: Remove HMAC from the webhook #10427 --- web/src/components/originui/number-input.tsx | 8 +++ web/src/components/ragflow-form.tsx | 10 +++- web/src/constants/agent.tsx | 2 +- web/src/pages/agent/constant/index.tsx | 12 +++-- web/src/pages/agent/form/begin-form/index.tsx | 1 + web/src/pages/agent/form/begin-form/schema.ts | 11 +++- .../form/begin-form/use-handle-mode-change.ts | 4 +- .../agent/form/begin-form/webhook/auth.tsx | 50 +++++-------------- .../begin-form/webhook/dynamic-request.tsx | 30 ++--------- .../agent/form/begin-form/webhook/index.tsx | 41 +++++++++++++-- .../begin-form/webhook/request-schema.tsx | 37 +++++++++++++- web/src/pages/agent/utils.ts | 16 ++++-- 12 files changed, 140 insertions(+), 82 deletions(-) diff --git a/web/src/components/originui/number-input.tsx b/web/src/components/originui/number-input.tsx index 8017d32c7..535c1e8b3 100644 --- a/web/src/components/originui/number-input.tsx +++ b/web/src/components/originui/number-input.tsx @@ -7,6 +7,7 @@ interface NumberInputProps { onChange?: (value: number) => void; height?: number | string; min?: number; + max?: number; } const NumberInput: React.FC = ({ @@ -15,6 +16,7 @@ const NumberInput: React.FC = ({ onChange, height, min = 0, + max = Infinity, }) => { const [value, setValue] = useState(() => { return initialValue ?? 0; @@ -34,6 +36,9 @@ const NumberInput: React.FC = ({ }; const handleIncrement = () => { + if (value > max - 1) { + return; + } setValue(value + 1); onChange?.(value + 1); }; @@ -41,6 +46,9 @@ const NumberInput: React.FC = ({ const handleChange = (e: React.ChangeEvent) => { const newValue = Number(e.target.value); if (!isNaN(newValue)) { + if (newValue > max) { + return; + } setValue(newValue); onChange?.(newValue); } diff --git a/web/src/components/ragflow-form.tsx b/web/src/components/ragflow-form.tsx index d7a94fb37..e1e3b832a 100644 --- a/web/src/components/ragflow-form.tsx +++ b/web/src/components/ragflow-form.tsx @@ -7,7 +7,11 @@ import { } from '@/components/ui/form'; import { cn } from '@/lib/utils'; import { ReactNode, cloneElement, isValidElement } from 'react'; -import { ControllerRenderProps, useFormContext } from 'react-hook-form'; +import { + ControllerRenderProps, + UseControllerProps, + useFormContext, +} from 'react-hook-form'; type RAGFlowFormItemProps = { name: string; @@ -18,7 +22,7 @@ type RAGFlowFormItemProps = { required?: boolean; labelClassName?: string; className?: string; -}; +} & Pick, 'rules'>; export function RAGFlowFormItem({ name, @@ -29,11 +33,13 @@ export function RAGFlowFormItem({ required = false, labelClassName, className, + rules, }: RAGFlowFormItemProps) { const form = useFormContext(); return ( ( - - - - - ), - [t], - ); - - const renderHmacAuth = useCallback( - () => ( - <> - - - - - - - - - + > ), [t], @@ -129,11 +100,14 @@ export function Auth() { [WebhookSecurityAuthType.Token]: renderTokenAuth, [WebhookSecurityAuthType.Basic]: renderBasicAuth, [WebhookSecurityAuthType.Jwt]: renderJwtAuth, - [WebhookSecurityAuthType.Hmac]: renderHmacAuth, [WebhookSecurityAuthType.None]: () => null, }; - return AuthMap[ - (authType ?? WebhookSecurityAuthType.None) as WebhookSecurityAuthType - ](); + return ( +
+ {AuthMap[ + (authType ?? WebhookSecurityAuthType.None) as WebhookSecurityAuthType + ]()} +
+ ); } diff --git a/web/src/pages/agent/form/begin-form/webhook/dynamic-request.tsx b/web/src/pages/agent/form/begin-form/webhook/dynamic-request.tsx index 3e912e36a..2124f5e18 100644 --- a/web/src/pages/agent/form/begin-form/webhook/dynamic-request.tsx +++ b/web/src/pages/agent/form/begin-form/webhook/dynamic-request.tsx @@ -6,15 +6,10 @@ import { Separator } from '@/components/ui/separator'; import { Switch } from '@/components/ui/switch'; import { buildOptions } from '@/utils/form'; import { loader } from '@monaco-editor/react'; -import { omit } from 'lodash'; import { X } from 'lucide-react'; import { ReactNode } from 'react'; -import { useFieldArray, useFormContext, useWatch } from 'react-hook-form'; -import { - TypesWithArray, - WebhookContentType, - WebhookRequestParameters, -} from '../../../constant'; +import { useFieldArray, useFormContext } from 'react-hook-form'; +import { TypesWithArray, WebhookRequestParameters } from '../../../constant'; import { DynamicFormHeader } from '../../components/dynamic-fom-header'; loader.config({ paths: { vs: '/vs' } }); @@ -28,16 +23,9 @@ type SelectKeysProps = { requiredField?: string; nodeId?: string; isObject?: boolean; + operatorList: WebhookRequestParameters[]; }; -function buildParametersOptions(isObject: boolean) { - const list = isObject - ? WebhookRequestParameters - : omit(WebhookRequestParameters, ['File']); - - return buildOptions(list); -} - export function DynamicRequest({ name, label, @@ -45,15 +33,9 @@ export function DynamicRequest({ keyField = 'key', operatorField = 'type', requiredField = 'required', - isObject = false, + operatorList, }: SelectKeysProps) { const form = useFormContext(); - const contentType = useWatch({ - name: 'content_types', - control: form.control, - }); - const isFormDataContentType = - contentType === WebhookContentType.MultipartFormData; const { fields, remove, append } = useFieldArray({ name: name, @@ -94,9 +76,7 @@ export function DynamicRequest({ onChange={(val) => { field.onChange(val); }} - options={buildParametersOptions( - isObject && isFormDataContentType, - )} + options={buildOptions(operatorList)} > )} diff --git a/web/src/pages/agent/form/begin-form/webhook/index.tsx b/web/src/pages/agent/form/begin-form/webhook/index.tsx index 8bcae0f29..feec9847b 100644 --- a/web/src/pages/agent/form/begin-form/webhook/index.tsx +++ b/web/src/pages/agent/form/begin-form/webhook/index.tsx @@ -1,17 +1,20 @@ import { Collapse } from '@/components/collapse'; import CopyToClipboard from '@/components/copy-to-clipboard'; +import NumberInput from '@/components/originui/number-input'; import { SelectWithSearch } from '@/components/originui/select-with-search'; import { RAGFlowFormItem } from '@/components/ragflow-form'; -import { Input } from '@/components/ui/input'; import { MultiSelect } from '@/components/ui/multi-select'; import { Textarea } from '@/components/ui/textarea'; import { buildOptions } from '@/utils/form'; +import { useCallback } from 'react'; +import { useFormContext, useWatch } from 'react-hook-form'; import { useTranslation } from 'react-i18next'; import { useParams } from 'umi'; import { RateLimitPerList, WebhookMaxBodySize, WebhookMethod, + WebhookRateLimitPer, WebhookSecurityAuthType, } from '../../../constant'; import { DynamicStringForm } from '../../components/dynamic-string-form'; @@ -21,9 +24,26 @@ import { WebhookResponse } from './response'; const RateLimitPerOptions = buildOptions(RateLimitPerList); +const RequestLimitMap = { + [WebhookRateLimitPer.Second]: 100, + [WebhookRateLimitPer.Minute]: 1000, + [WebhookRateLimitPer.Hour]: 10000, + [WebhookRateLimitPer.Day]: 100000, +}; + export function WebHook() { const { t } = useTranslation(); const { id } = useParams(); + const form = useFormContext(); + + const rateLimitPer = useWatch({ + name: 'security.rate_limit.per', + control: form.control, + }); + + const getLimitRateLimitPerMax = useCallback((rateLimitPer: string) => { + return RequestLimitMap[rateLimitPer as keyof typeof RequestLimitMap] ?? 100; + }, []); const text = `${location.protocol}//${location.host}/api/v1/webhook/${id}`; @@ -61,13 +81,28 @@ export function WebHook() { name="security.rate_limit.limit" label={t('flow.webhook.limit')} > - + - + {(field) => ( + { + field.onChange(val); + form.setValue( + 'security.rate_limit.limit', + getLimitRateLimitPerMax(val), + ); + }} + > + )} { + return isFormDataContentType + ? [ + WebhookRequestParameters.String, + WebhookRequestParameters.Number, + WebhookRequestParameters.Boolean, + WebhookRequestParameters.File, + ] + : [ + WebhookRequestParameters.String, + WebhookRequestParameters.Number, + WebhookRequestParameters.Boolean, + ]; + }, [isFormDataContentType]); return ( {t('flow.webhook.schema')}}> @@ -23,14 +50,20 @@ export function WebhookRequestSchema() { diff --git a/web/src/pages/agent/utils.ts b/web/src/pages/agent/utils.ts index 4f3df85c3..3c5f5e961 100644 --- a/web/src/pages/agent/utils.ts +++ b/web/src/pages/agent/utils.ts @@ -34,6 +34,7 @@ import { NodeHandleId, Operator, TypesWithArray, + WebhookSecurityAuthType, } from './constant'; import { BeginFormSchemaType } from './form/begin-form/schema'; import { DataOperationsFormSchemaType } from './form/data-operations-form'; @@ -348,13 +349,20 @@ function transformRequestSchemaToJsonschema( function transformBeginParams(params: BeginFormSchemaType) { if (params.mode === AgentDialogueMode.Webhook) { + const nextSecurity: Record = { + ...params.security, + ip_whitelist: params.security?.ip_whitelist.map((x) => x.value), + }; + if (params.security?.auth_type === WebhookSecurityAuthType.Jwt) { + nextSecurity.jwt = { + ...nextSecurity.jwt, + required_claims: nextSecurity.jwt?.required_claims.map((x) => x.value), + }; + } return { ...params, schema: transformRequestSchemaToJsonschema(params.schema), - security: { - ...params.security, - ip_whitelist: params.security?.ip_whitelist.map((x) => x.value), - }, + security: nextSecurity, }; }