mirror of https://github.com/ospab/ostp.git
Remove Reality/XTLS from all UI components and TSX pages (Dashboard, Settings, Tools)
This commit is contained in:
parent
a8802cd50f
commit
7fe1be33fc
|
|
@ -137,17 +137,7 @@ export default function Dashboard() {
|
|||
label={t('db_api_bind')}
|
||||
value={config?.api?.enabled ? config.api.bind : 'Disabled'}
|
||||
/>
|
||||
<InfoItem
|
||||
label={t('db_reality_status')}
|
||||
value={config?.reality?.enabled ? 'Active' : 'Disabled'}
|
||||
highlight={config?.reality?.enabled}
|
||||
/>
|
||||
{config?.reality?.enabled && (
|
||||
<InfoItem
|
||||
label={t('db_reality_dest')}
|
||||
value={config.reality.dest}
|
||||
/>
|
||||
)}
|
||||
|
||||
<InfoItem
|
||||
label={t('db_fallback_status')}
|
||||
value={config?.fallback?.enabled ? `Active` : 'Disabled'}
|
||||
|
|
|
|||
|
|
@ -362,87 +362,7 @@ export default function Settings() {
|
|||
</div>
|
||||
</div>
|
||||
|
||||
{/* SECTION: REALITY MASQUERADE */}
|
||||
<div className="glass-panel rounded-2xl p-6 space-y-4">
|
||||
<h2 className="text-lg font-bold border-b border-white/5 pb-2 text-white flex items-center gap-2">
|
||||
<Globe className="w-5 h-5 text-purple-400" /> {t('st_ui_rl_title')}
|
||||
</h2>
|
||||
|
||||
<div className="flex items-center gap-3 mb-2">
|
||||
<input
|
||||
type="checkbox"
|
||||
id="reality-enabled"
|
||||
className="w-4 h-4 accent-primary rounded bg-white/5 border-white/10"
|
||||
checked={config.reality?.enabled || false}
|
||||
onChange={(e) => updateConfigField(['reality', 'enabled'], e.target.checked)}
|
||||
/>
|
||||
<label htmlFor="reality-enabled" className="text-sm font-medium text-white cursor-pointer select-none">
|
||||
{t('st_ui_rl_enable')}
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<div className="space-y-2 col-span-1 md:col-span-2">
|
||||
<label className="text-sm font-semibold text-text-muted uppercase">{t('st_ui_rl_dest')}</label>
|
||||
<input
|
||||
type="text"
|
||||
disabled={!config.reality?.enabled}
|
||||
className="w-full bg-white/5 border border-white/10 rounded-xl px-4 py-2.5 text-white font-mono disabled:opacity-50"
|
||||
placeholder="e.g. www.microsoft.com:443"
|
||||
value={config.reality?.dest || ''}
|
||||
onChange={(e) => updateConfigField(['reality', 'dest'], e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<label className="text-sm font-semibold text-text-muted uppercase">{t('st_ui_rl_pri')}</label>
|
||||
<input
|
||||
type="text"
|
||||
disabled={!config.reality?.enabled}
|
||||
className="w-full bg-white/5 border border-white/10 rounded-xl px-4 py-2.5 text-white font-mono disabled:opacity-50 text-xs"
|
||||
value={config.reality?.private_key || ''}
|
||||
onChange={(e) => updateConfigField(['reality', 'private_key'], e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<label className="text-sm font-semibold text-text-muted uppercase">{t('st_ui_rl_pub')}</label>
|
||||
<input
|
||||
type="text"
|
||||
disabled={!config.reality?.enabled}
|
||||
className="w-full bg-white/5 border border-white/10 rounded-xl px-4 py-2.5 text-white font-mono disabled:opacity-50 text-xs"
|
||||
value={config.reality?.pbk || ''}
|
||||
onChange={(e) => updateConfigField(['reality', 'pbk'], e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<label className="text-sm font-semibold text-text-muted uppercase">{t('st_ui_rl_sid')}</label>
|
||||
<input
|
||||
type="text"
|
||||
disabled={!config.reality?.enabled}
|
||||
className="w-full bg-white/5 border border-white/10 rounded-xl px-4 py-2.5 text-white font-mono disabled:opacity-50"
|
||||
value={config.reality?.sid || ''}
|
||||
onChange={(e) => updateConfigField(['reality', 'sid'], e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<label className="text-sm font-semibold text-text-muted uppercase">{t('st_ui_rl_sni')}</label>
|
||||
<input
|
||||
type="text"
|
||||
disabled={!config.reality?.enabled}
|
||||
className="w-full bg-white/5 border border-white/10 rounded-xl px-4 py-2.5 text-white font-mono disabled:opacity-50"
|
||||
placeholder="e.g. www.microsoft.com, microsoft.com"
|
||||
value={config.reality?.sni_list ? config.reality.sni_list.join(', ') : ''}
|
||||
onChange={(e) => {
|
||||
const list = e.target.value.split(',').map(s => s.trim()).filter(Boolean);
|
||||
updateConfigField(['reality', 'sni_list'], list);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* SECTION: OUTBOUND ROUTING */}
|
||||
<div className="glass-panel rounded-2xl p-6 space-y-4">
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { useState, useRef } from 'react';
|
||||
import { Wrench, Key, Download, Upload, RefreshCw, CheckCircle, XCircle, AlertTriangle, ShieldAlert, Copy } from 'lucide-react';
|
||||
import { Wrench, Download, Upload, RefreshCw, CheckCircle, XCircle, ShieldAlert } from 'lucide-react';
|
||||
import { useLanguage } from '../lib/LanguageContext';
|
||||
import { api, getApiSettings } from '../lib/api';
|
||||
import { addAuditLog } from '../lib/audit';
|
||||
|
|
@ -7,12 +7,7 @@ import { addAuditLog } from '../lib/audit';
|
|||
export default function Tools() {
|
||||
const { t, language } = useLanguage();
|
||||
|
||||
// Keygen State
|
||||
const [keys, setKeys] = useState<{ publicKey: string; privateKey: string; sid: string; isFallback?: boolean } | null>(null);
|
||||
const [isGenerating, setIsGenerating] = useState(false);
|
||||
const [isCopied, setIsCopied] = useState(false);
|
||||
|
||||
// Backup State
|
||||
const [isExporting, setIsExporting] = useState(false);
|
||||
const [isImporting, setIsImporting] = useState(false);
|
||||
const [importResult, setImportResult] = useState<{ success: boolean; message: string } | null>(null);
|
||||
|
|
@ -23,99 +18,6 @@ export default function Tools() {
|
|||
const [isPinging, setIsPinging] = useState(false);
|
||||
const [pingResult, setPingResult] = useState<{ success: boolean; message: string } | null>(null);
|
||||
|
||||
// Reality X25519 Key Generator using Web Crypto
|
||||
const handleGenerateKeys = async () => {
|
||||
setIsGenerating(true);
|
||||
setKeys(null);
|
||||
setIsCopied(false);
|
||||
|
||||
// Simulate slight lag for UI satisfaction
|
||||
await new Promise(resolve => setTimeout(resolve, 600));
|
||||
|
||||
try {
|
||||
// Try using modern browser Web Crypto for X25519
|
||||
const keypair = await window.crypto.subtle.generateKey(
|
||||
{ name: 'X25519' },
|
||||
true,
|
||||
['deriveBits']
|
||||
);
|
||||
|
||||
const pubBuffer = await window.crypto.subtle.exportKey('raw', keypair.publicKey);
|
||||
const priPkcs8 = await window.crypto.subtle.exportKey('pkcs8', keypair.privateKey);
|
||||
|
||||
// Raw private key bytes are at the end of PKCS#8 ASN.1 wrapper for X25519 (last 32 bytes)
|
||||
const priBuffer = priPkcs8.slice(priPkcs8.byteLength - 32);
|
||||
|
||||
const toBase64 = (buf: ArrayBuffer) => {
|
||||
const bytes = new Uint8Array(buf);
|
||||
let binary = '';
|
||||
for (let i = 0; i < bytes.byteLength; i++) {
|
||||
binary += String.fromCharCode(bytes[i]);
|
||||
}
|
||||
return btoa(binary)
|
||||
.replace(/\+/g, '-')
|
||||
.replace(/\//g, '_')
|
||||
.replace(/=+$/, ''); // URL-safe base64 unpadded
|
||||
};
|
||||
|
||||
const pubKey = toBase64(pubBuffer);
|
||||
const priKey = toBase64(priBuffer);
|
||||
|
||||
// Generate random 8-byte (16-char hex) SID
|
||||
const sidBytes = new Uint8Array(8);
|
||||
window.crypto.getRandomValues(sidBytes);
|
||||
const sid = Array.from(sidBytes).map(b => b.toString(16).padStart(2, '0')).join('');
|
||||
|
||||
setKeys({ publicKey: pubKey, privateKey: priKey, sid });
|
||||
addAuditLog(
|
||||
'Generated Reality X25519 keypair in browser',
|
||||
'Сгенерирована пара ключей Reality X25519 в браузере',
|
||||
true
|
||||
);
|
||||
} catch (err: any) {
|
||||
console.warn("Web Crypto X25519 unsupported, using pseudo-random fallback keys", err);
|
||||
|
||||
// Fallback pseudo-random base64 keys
|
||||
const randomBase64 = (len: number) => {
|
||||
const bytes = new Uint8Array(len);
|
||||
window.crypto.getRandomValues(bytes);
|
||||
let binary = '';
|
||||
for (let i = 0; i < len; i++) {
|
||||
binary += String.fromCharCode(bytes[i]);
|
||||
}
|
||||
return btoa(binary)
|
||||
.replace(/\+/g, '-')
|
||||
.replace(/\//g, '_')
|
||||
.replace(/=+$/, '');
|
||||
};
|
||||
|
||||
const sidBytes = new Uint8Array(8);
|
||||
window.crypto.getRandomValues(sidBytes);
|
||||
const sid = Array.from(sidBytes).map(b => b.toString(16).padStart(2, '0')).join('');
|
||||
|
||||
setKeys({
|
||||
publicKey: randomBase64(32),
|
||||
privateKey: randomBase64(32),
|
||||
sid,
|
||||
isFallback: true
|
||||
});
|
||||
addAuditLog(
|
||||
'Generated fallback Reality keypair (pseudo-random)',
|
||||
'Сгенерированы резервные ключи Reality (псевдослучайные)',
|
||||
true
|
||||
);
|
||||
} finally {
|
||||
setIsGenerating(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleCopyKeys = () => {
|
||||
if (!keys) return;
|
||||
const text = `private_key: ${keys.privateKey}\npbk: ${keys.publicKey}\nsid: ${keys.sid}`;
|
||||
navigator.clipboard.writeText(text);
|
||||
setIsCopied(true);
|
||||
setTimeout(() => setIsCopied(false), 2000);
|
||||
};
|
||||
|
||||
// Export config.json
|
||||
const handleExportConfig = async () => {
|
||||
|
|
@ -210,68 +112,10 @@ export default function Tools() {
|
|||
<h1 className="text-3xl font-bold tracking-tight mb-1 flex items-center gap-3">
|
||||
<Wrench className="w-8 h-8 text-primary" /> {t('tl_title')}
|
||||
</h1>
|
||||
<p className="text-text-muted">{t('tl_subtitle')}</p>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
||||
|
||||
{/* Reality Keypair Generator */}
|
||||
<div className="glass-panel rounded-2xl p-6 space-y-4 flex flex-col justify-between">
|
||||
<div>
|
||||
<h2 className="text-xl font-semibold mb-2 flex items-center gap-2">
|
||||
<Key className="w-5 h-5 text-primary" /> {t('tl_keygen_title')}
|
||||
</h2>
|
||||
<p className="text-sm text-text-muted leading-relaxed mb-4">
|
||||
{t('tl_keygen_desc')}
|
||||
</p>
|
||||
|
||||
{keys && (
|
||||
<div className="space-y-3 bg-black/30 border border-white/5 p-4 rounded-xl font-mono text-xs">
|
||||
{keys.isFallback && (
|
||||
<div className="flex items-start gap-2 text-yellow-400 bg-yellow-500/10 p-2.5 rounded-lg mb-2">
|
||||
<AlertTriangle className="w-4 h-4 shrink-0 mt-0.5" />
|
||||
<span>
|
||||
{language === 'ru'
|
||||
? 'Браузер не поддерживает криптографию Curve25519 (X25519). Ключи сгенерированы в тестовом режиме. Рекомендуется использовать CLI ядра: ostp --generate-key.'
|
||||
: 'Web Crypto API does not support Curve25519 (X25519) in this browser. Generated pseudo-random keys. For production, run: ostp --generate-key.'}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
<div>
|
||||
<span className="text-text-muted">private_key: </span>
|
||||
<span className="text-secondary select-all">{keys.privateKey}</span>
|
||||
</div>
|
||||
<div>
|
||||
<span className="text-text-muted">pbk (public_key): </span>
|
||||
<span className="text-primary select-all">{keys.publicKey}</span>
|
||||
</div>
|
||||
<div>
|
||||
<span className="text-text-muted">sid (session ID): </span>
|
||||
<span className="text-white select-all">{keys.sid}</span>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="flex gap-3 pt-4 border-t border-white/5">
|
||||
<button
|
||||
onClick={handleGenerateKeys}
|
||||
disabled={isGenerating}
|
||||
className="flex items-center gap-2 bg-primary hover:bg-primary/90 text-white px-5 py-2.5 rounded-xl font-semibold transition-colors disabled:opacity-50"
|
||||
>
|
||||
<RefreshCw className={`w-4 h-4 ${isGenerating ? 'animate-spin' : ''}`} /> {t('tl_keygen_btn')}
|
||||
</button>
|
||||
{keys && (
|
||||
<button
|
||||
onClick={handleCopyKeys}
|
||||
className="flex items-center gap-2 bg-white/5 hover:bg-white/10 text-white px-5 py-2.5 rounded-xl border border-white/10 text-sm transition-colors"
|
||||
>
|
||||
<Copy className="w-4 h-4" /> {isCopied ? t('cl_copied') : t('tl_keygen_copy')}
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Backup & Restore Configuration */}
|
||||
<div className="glass-panel rounded-2xl p-6 space-y-4 flex flex-col justify-between">
|
||||
<div>
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev
|
|||
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
|
||||
# In Windows, build-name is used as the major, minor, and patch parts
|
||||
# of the product and file versions while build-number is used as the build suffix.
|
||||
version: 0.2.87+2
|
||||
version: 0.2.90+5
|
||||
|
||||
environment:
|
||||
sdk: ^3.11.4
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"$schema": "https://schema.tauri.app/config/2",
|
||||
"productName": "ostp-gui",
|
||||
"version": "0.2.87",
|
||||
"version": "0.2.90",
|
||||
"identifier": "com.ospab.ostp",
|
||||
"build": {
|
||||
"frontendDist": "../src"
|
||||
|
|
|
|||
|
|
@ -625,11 +625,9 @@ fn run_setup_wizard(config_path: &std::path::Path) -> Result<()> {
|
|||
// Try import from link first
|
||||
let use_link = wizard_yn("Do you have a share link (ostp://...)?", false);
|
||||
let (server, access_key, sni, transport_mode) = if use_link {
|
||||
let link = wizard_prompt("Paste link", "");
|
||||
let url = url::Url::parse(&link).unwrap();
|
||||
let mut p = url.query_pairs();
|
||||
let sni = p.find(|(k, _)| k == "sni").map(|(_, v)| v.to_string()).unwrap_or_default();
|
||||
let tm = p.find(|(k, _)| k == "type").map(|(_, v)| v.to_string()).unwrap_or("udp".to_string());
|
||||
let sni = p.find(|(k, _)| k == "sni").map(|(_, v): (&str, std::borrow::Cow<str>)| v.to_string()).unwrap_or_default();
|
||||
let tm = p.find(|(k, _)| k == "type").map(|(_, v): (&str, std::borrow::Cow<str>)| v.to_string()).unwrap_or("udp".to_string());
|
||||
(url.host_str().unwrap().to_string() + ":" + &url.port().unwrap_or(50000).to_string(), url.username().to_string(), sni, tm)
|
||||
} else {
|
||||
("127.0.0.1:50000".to_string(), "".to_string(), "".to_string(), "udp".to_string())
|
||||
|
|
@ -684,7 +682,7 @@ fn run_setup_wizard(config_path: &std::path::Path) -> Result<()> {
|
|||
|
||||
// Build and save config
|
||||
let key_for_gen = generate_secure_key("hex"); // unused but needed for init template
|
||||
let effective_sni = sni;
|
||||
let effective_sni = sni;
|
||||
let _ = key_for_gen;
|
||||
|
||||
let client_json = serde_json::json!({
|
||||
|
|
@ -1369,7 +1367,7 @@ async fn run_app() -> Result<()> {
|
|||
"sessions": 1
|
||||
}},
|
||||
"debug": false
|
||||
}}"#, key)
|
||||
}}"#, key, pub_key, sid)
|
||||
};
|
||||
if let Some(parent) = args.config.parent() {
|
||||
if !parent.as_os_str().is_empty() {
|
||||
|
|
@ -1388,30 +1386,13 @@ async fn run_app() -> Result<()> {
|
|||
let mut link = format!("ostp://{}@{}:50000", key.key(), host);
|
||||
let mut query_params = Vec::new();
|
||||
|
||||
|
||||
|
||||
if let Some(t) = &s.transport {
|
||||
if let Some(mode) = &t.mode {
|
||||
if mode == "uot" {
|
||||
query_params.push("type=tcp".to_string());
|
||||
} else {
|
||||
query_params.push("type=udp".to_string());
|
||||
}
|
||||
}
|
||||
if let Some(sni) = &t.stealth_sni {
|
||||
// If reality is not enabled, add stealth_sni to link so client configures it
|
||||
if !sni.is_empty() {
|
||||
query_params.push(format!("sni={}", sni));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
query_params.push("type=udp".to_string());
|
||||
query_params.push("type=udp".to_string());
|
||||
}
|
||||
|
||||
if !query_params.is_empty() {
|
||||
link.push('?');
|
||||
link.push_str(&query_params.join("&"));
|
||||
}
|
||||
|
||||
println!("\n Share link for client distribution:");
|
||||
println!(" {}", link);
|
||||
}
|
||||
|
|
@ -1458,23 +1439,7 @@ async fn run_app() -> Result<()> {
|
|||
let mut link = format!("ostp://{}@{}:{}", key.key(), host, port);
|
||||
let mut query_params = Vec::new();
|
||||
|
||||
|
||||
|
||||
if let Some(t) = &server_cfg.transport {
|
||||
if let Some(mode) = &t.mode {
|
||||
if mode == "uot" {
|
||||
query_params.push("type=tcp".to_string());
|
||||
} else {
|
||||
query_params.push("type=udp".to_string());
|
||||
}
|
||||
}
|
||||
if let Some(sni) = &t.stealth_sni {
|
||||
if !sni.is_empty() {
|
||||
query_params.push(format!("sni={}", sni));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
query_params.push("type=udp".to_string());
|
||||
query_params.push("type=udp".to_string());
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -1482,7 +1447,7 @@ async fn run_app() -> Result<()> {
|
|||
if !query_params.is_empty() {
|
||||
link.push('?');
|
||||
link.push_str(&query_params.join("&"));
|
||||
}
|
||||
|
||||
println!(" [{}] {}", idx + 1, link);
|
||||
}
|
||||
return Ok(());
|
||||
|
|
|
|||
Loading…
Reference in New Issue