mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-11-06 21:25:04 +03:00
feat(tls,reality): port xray TLS/REALITY fields, cert-hash helpers, fallback UX
TLS: add verifyPeerCertByName (vcn) to inbound settings + emit in both share-link generators (frontend + Go sub) and outbound parser; the allowInsecure replacement xray removed after 2026-06-01. Add server-side curvePreferences, masterKeyLog, echSockopt (passthrough + form) at tlsSettings top-level so they survive the panel-only settings strip. REALITY: add limitFallbackUpload/Download (afterBytes/bytesPerSec/burstBytesPerSec) with per-field tooltips, plus masterKeyLog. Verified field names/semantics against pinned xray v1.260327.1 (bytesPerSec=0 disables). Hosts: fix verify_peer_cert_by_name column bool->string (xray expects comma-separated names) with an idempotent, history-gate-free migration (SQLite typeof blank; Postgres ALTER once); emit vcn for hosts/external proxies. Server: add getCertHash (local cert DER SHA-256) and getRemoteCertHash (xray tls ping) endpoints + api-docs; wire pinned-cert field buttons. Drop the meaningless random-hash button. Xray UI: metrics endpoint (listen/tag) config in Basics; import/export for routing rules and outbounds. Fallbacks card: compact empty state, header-aligned actions, responsive labeled grid rows. i18n: add all new keys to every locale; drop unused generateRandomPin.
This commit is contained in:
@@ -100,17 +100,66 @@ export function useSecurityActions({ form, setSaving, messageApi, nodeId }: UseS
|
||||
form.setFieldValue(['streamSettings', 'tlsSettings', 'settings', 'echConfigList'], '');
|
||||
};
|
||||
|
||||
const generateRandomPinHash = () => {
|
||||
const bytes = new Uint8Array(32);
|
||||
crypto.getRandomValues(bytes);
|
||||
const hash = Array.from(bytes, (b) => b.toString(16).padStart(2, '0')).join('');
|
||||
const current = (form.getFieldValue(
|
||||
['streamSettings', 'tlsSettings', 'settings', 'pinnedPeerCertSha256'],
|
||||
) as string[] | undefined) ?? [];
|
||||
form.setFieldValue(
|
||||
['streamSettings', 'tlsSettings', 'settings', 'pinnedPeerCertSha256'],
|
||||
[...current, hash],
|
||||
);
|
||||
// Fill the pinned-cert field from the inbound's own certificate: read the
|
||||
// first configured cert (file path or inline content) and ask the server for
|
||||
// its hex SHA-256, then merge the hash(es) into pinnedPeerCertSha256.
|
||||
const pinFromCert = async () => {
|
||||
const certs = (form.getFieldValue(['streamSettings', 'tlsSettings', 'certificates']) ?? []) as Array<{
|
||||
certificateFile?: string;
|
||||
certificate?: string[];
|
||||
}>;
|
||||
const first = certs[0];
|
||||
const certFile = first?.certificateFile?.trim() ?? '';
|
||||
const certContent = Array.isArray(first?.certificate) ? first.certificate.join('\n').trim() : '';
|
||||
if (!certFile && !certContent) {
|
||||
messageApi.warning(t('pages.inbounds.setDefaultCertEmpty'));
|
||||
return;
|
||||
}
|
||||
setSaving(true);
|
||||
try {
|
||||
const msg = await HttpUtil.post('/panel/api/server/getCertHash', { certFile, certContent });
|
||||
if (!msg?.success) {
|
||||
messageApi.warning(msg?.msg || t('pages.inbounds.setDefaultCertEmpty'));
|
||||
return;
|
||||
}
|
||||
const hashes = (msg.obj as string[] | undefined) ?? [];
|
||||
if (hashes.length === 0) return;
|
||||
const current = (form.getFieldValue(
|
||||
['streamSettings', 'tlsSettings', 'settings', 'pinnedPeerCertSha256'],
|
||||
) as string[] | undefined) ?? [];
|
||||
const merged = Array.from(new Set([...current, ...hashes]));
|
||||
form.setFieldValue(['streamSettings', 'tlsSettings', 'settings', 'pinnedPeerCertSha256'], merged);
|
||||
} finally {
|
||||
setSaving(false);
|
||||
}
|
||||
};
|
||||
|
||||
// Fill the pinned-cert field by pinging the configured SNI: fetches the live
|
||||
// remote certificate hash via `xray tls ping`. Useful when the panel doesn't
|
||||
// hold the cert file (a CDN front / external endpoint).
|
||||
const pinFromRemote = async () => {
|
||||
const server = ((form.getFieldValue(['streamSettings', 'tlsSettings', 'serverName']) as string | undefined) ?? '').trim();
|
||||
if (!server) {
|
||||
messageApi.warning(t('pages.inbounds.form.pinFromRemoteNoSni'));
|
||||
return;
|
||||
}
|
||||
setSaving(true);
|
||||
try {
|
||||
const msg = await HttpUtil.post('/panel/api/server/getRemoteCertHash', { server });
|
||||
if (!msg?.success) {
|
||||
messageApi.warning(msg?.msg || t('pages.inbounds.form.pinFromRemoteFailed'));
|
||||
return;
|
||||
}
|
||||
const hashes = (msg.obj as string[] | undefined) ?? [];
|
||||
if (hashes.length === 0) return;
|
||||
const current = (form.getFieldValue(
|
||||
['streamSettings', 'tlsSettings', 'settings', 'pinnedPeerCertSha256'],
|
||||
) as string[] | undefined) ?? [];
|
||||
const merged = Array.from(new Set([...current, ...hashes]));
|
||||
form.setFieldValue(['streamSettings', 'tlsSettings', 'settings', 'pinnedPeerCertSha256'], merged);
|
||||
} finally {
|
||||
setSaving(false);
|
||||
}
|
||||
};
|
||||
|
||||
const setCertFromPanel = async (certName: number) => {
|
||||
@@ -194,7 +243,8 @@ export function useSecurityActions({ form, setSaving, messageApi, nodeId }: UseS
|
||||
randomizeShortIds,
|
||||
getNewEchCert,
|
||||
clearEchCert,
|
||||
generateRandomPinHash,
|
||||
pinFromCert,
|
||||
pinFromRemote,
|
||||
setCertFromPanel,
|
||||
clearCertFiles,
|
||||
onSecurityChange,
|
||||
|
||||
Reference in New Issue
Block a user