From 8db1be859264c2bcce40547f4333c10f66652706 Mon Sep 17 00:00:00 2001 From: MHSanaei Date: Tue, 26 May 2026 02:36:11 +0200 Subject: [PATCH] feat(frontend): security tab Reality + ECH + mldsa65 controls (Pattern A) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds the Reality sub-form and the four API-call buttons that drive the server-generated material: - genRealityKeypair calls /panel/api/server/getNewX25519Cert and writes the result into ['streamSettings', 'realitySettings', 'privateKey'] and the nested settings.publicKey path. - genMldsa65 calls /panel/api/server/getNewmldsa65 for the post-quantum seed/verify pair. - getNewEchCert calls /panel/api/server/getNewEchCert with the current serverName and writes echServerKeys + settings.echConfigList. - randomizeRealityTarget seeds target + serverNames from the random reality-targets pool. - randomizeShortIds calls RandomUtil.randomShortIds (comma-joined string) and splits into the schema's string[] form. Reality fields are bound directly to schema paths — show/xver/target, maxTimediff, min/max ClientVer, the settings.{publicKey, fingerprint, spiderX, mldsa65Verify} nested subtree, plus the array fields (serverNames, shortIds) rendered as Select mode="tags" since both ship as string[] on the wire. TLS certificates list (Form.List with the useFile DU) still pending — that's a chunky sub-form on its own. --- .../pages/inbounds/InboundFormModal.new.tsx | 215 ++++++++++++++++++ 1 file changed, 215 insertions(+) diff --git a/frontend/src/pages/inbounds/InboundFormModal.new.tsx b/frontend/src/pages/inbounds/InboundFormModal.new.tsx index 9459a290..9bae57f7 100644 --- a/frontend/src/pages/inbounds/InboundFormModal.new.tsx +++ b/frontend/src/pages/inbounds/InboundFormModal.new.tsx @@ -31,6 +31,7 @@ import { isSS2022, } from '@/lib/xray/protocol-capabilities'; import { SSMethodSchema } from '@/schemas/protocols/inbound/shadowsocks'; +import { getRandomRealityTarget } from '@/models/reality-targets'; import { InboundFormBaseSchema, InboundFormSchema, @@ -128,6 +129,80 @@ export default function InboundFormModalNew({ const tlsAllowed = canEnableTls({ protocol, streamSettings: { network, security } }); const realityAllowed = canEnableReality({ protocol, streamSettings: { network, security } }); + const genRealityKeypair = async () => { + setSaving(true); + try { + const msg = await HttpUtil.get('/panel/api/server/getNewX25519Cert'); + if (msg?.success) { + const obj = msg.obj as { privateKey: string; publicKey: string }; + form.setFieldValue(['streamSettings', 'realitySettings', 'privateKey'], obj.privateKey); + form.setFieldValue(['streamSettings', 'realitySettings', 'settings', 'publicKey'], obj.publicKey); + } + } finally { + setSaving(false); + } + }; + + const clearRealityKeypair = () => { + form.setFieldValue(['streamSettings', 'realitySettings', 'privateKey'], ''); + form.setFieldValue(['streamSettings', 'realitySettings', 'settings', 'publicKey'], ''); + }; + + const genMldsa65 = async () => { + setSaving(true); + try { + const msg = await HttpUtil.get('/panel/api/server/getNewmldsa65'); + if (msg?.success) { + const obj = msg.obj as { seed: string; verify: string }; + form.setFieldValue(['streamSettings', 'realitySettings', 'mldsa65Seed'], obj.seed); + form.setFieldValue(['streamSettings', 'realitySettings', 'settings', 'mldsa65Verify'], obj.verify); + } + } finally { + setSaving(false); + } + }; + + const clearMldsa65 = () => { + form.setFieldValue(['streamSettings', 'realitySettings', 'mldsa65Seed'], ''); + form.setFieldValue(['streamSettings', 'realitySettings', 'settings', 'mldsa65Verify'], ''); + }; + + const randomizeRealityTarget = () => { + const tgt = getRandomRealityTarget() as { target: string; sni: string }; + form.setFieldValue(['streamSettings', 'realitySettings', 'target'], tgt.target); + form.setFieldValue( + ['streamSettings', 'realitySettings', 'serverNames'], + tgt.sni.split(',').map((s) => s.trim()).filter(Boolean), + ); + }; + + const randomizeShortIds = () => { + form.setFieldValue( + ['streamSettings', 'realitySettings', 'shortIds'], + RandomUtil.randomShortIds().split(',').map((s) => s.trim()).filter(Boolean), + ); + }; + + const getNewEchCert = async () => { + const sni = form.getFieldValue(['streamSettings', 'tlsSettings', 'serverName']); + setSaving(true); + try { + const msg = await HttpUtil.post('/panel/api/server/getNewEchCert', { sni }); + if (msg?.success) { + const obj = msg.obj as { echServerKeys: string; echConfigList: string }; + form.setFieldValue(['streamSettings', 'tlsSettings', 'echServerKeys'], obj.echServerKeys); + form.setFieldValue(['streamSettings', 'tlsSettings', 'settings', 'echConfigList'], obj.echConfigList); + } + } finally { + setSaving(false); + } + }; + + const clearEchCert = () => { + form.setFieldValue(['streamSettings', 'tlsSettings', 'echServerKeys'], ''); + form.setFieldValue(['streamSettings', 'tlsSettings', 'settings', 'echConfigList'], ''); + }; + const onSecurityChange = (next: string) => { const current = (form.getFieldValue('streamSettings') as Record) ?? {}; const cleaned: Record = { ...current, security: next }; @@ -1480,6 +1555,146 @@ export default function InboundFormModalNew({ > + + + + + + + + + + + + + + + )} + + {security === 'reality' && ( + <> + + + + + + + + + + + Target{' '} + + + } + > + + + + SNI{' '} + + + } + > + + + + + + + Short IDs{' '} + + + } + > + + + + + + + + + + + + + + + + + + + + + + + + + + )}