mirror of
https://github.com/J3vb/OwnCord.git
synced 2026-09-03 03:50:00 +03:00
The REST path previously used tauri-plugin-http with danger.acceptInvalidCerts, so it accepted ANY certificate while the WS and LiveKit paths were TOFU-pinned in Rust — and the bearer token rides every REST request. This routes REST through a new Rust loopback TCP->TLS proxy that pins the server certificate to the same trust-on-first-use fingerprint as the WS proxy. Rust (src-tauri): - New http_proxy.rs: per-host loopback tunnels (HttpProxyState map); per-connection TOFU via CaptureVerifier + tofu_check, sharing ws_proxy's cert store (cert_store_key) and emitting the same cert-tofu events (first-use banner / mismatch modal). First request's Host is rewritten and Connection: close injected so one request rides each connection. Mismatch returns a clean 502 to the loopback fetch. - Register HttpProxyState + start_http_proxy/stop_http_proxy in lib.rs. - Drop the dangerous-settings feature from tauri-plugin-http. TypeScript (src): - New lib/httpProxy.ts: ensureHttpProxy(host) (per-host cache + concurrent-start dedup) / stopHttpProxy(host). - api.ts, profiles.ts (health), attachments.ts (image + download) resolve server URLs to http://127.0.0.1:{port}; remove the allowSelfSigned config field and every acceptInvalidCerts block. External hosts (CDNs, OG previews, YouTube) keep normal TLS validation. - main.ts constructs the API client without allowSelfSigned. - capabilities/default.json: allow http://127.0.0.1:* fetch scope. Tests: - New tests/unit/http-proxy.test.ts (cache, dedup, stop/restart). - api.test.ts and attachments-render.test.ts: mock httpProxy, replace the acceptInvalidCerts assertions with proxy-origin assertions. Verified: tsc --noEmit clean; new + affected vitest suites green (176 tests); the http_proxy pure logic (host validation, header rewrite) passes as standalone Rust unit tests; oxlint/eslint counts unchanged from HEAD; prettier clean. The full Tauri build (cargo) requires GUI system libs not present in this environment and runs on CI/real runners. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UA17KPvqGBX3XbXYnMf1rA
60 lines
2.1 KiB
TypeScript
60 lines
2.1 KiB
TypeScript
// Client-side helper for the Rust HTTP TOFU proxy (closes audit A-2026-07-02).
|
|
//
|
|
// The Rust `http_proxy` module runs one loopback TCP→TLS tunnel per remote
|
|
// host and pins the server certificate with the same trust-on-first-use store
|
|
// as the WebSocket proxy. REST calls go to http://127.0.0.1:{port} instead of
|
|
// https://{host} directly, so the webview never has to accept an invalid
|
|
// certificate and the bearer token never rides an unpinned TLS connection.
|
|
//
|
|
// This module maps a remote host ("host" or "host:port") to its loopback
|
|
// origin ("http://127.0.0.1:{port}"), caching per host and de-duplicating
|
|
// concurrent starts so parallel requests share one tunnel.
|
|
|
|
import { invoke } from "@tauri-apps/api/core";
|
|
import { createLogger } from "./logger";
|
|
|
|
const log = createLogger("http-proxy");
|
|
|
|
/** host → resolved loopback origin (e.g. "http://127.0.0.1:49812"). */
|
|
const origins = new Map<string, string>();
|
|
/** host → in-flight start so concurrent callers don't race the tunnel. */
|
|
const pending = new Map<string, Promise<string>>();
|
|
|
|
/**
|
|
* Ensure a tunnel exists for `host` and return its loopback origin
|
|
* (no trailing slash). Idempotent and concurrency-safe per host.
|
|
*/
|
|
export async function ensureHttpProxy(host: string): Promise<string> {
|
|
const cached = origins.get(host);
|
|
if (cached) return cached;
|
|
|
|
const inFlight = pending.get(host);
|
|
if (inFlight) return inFlight;
|
|
|
|
const start = (async () => {
|
|
const port = await invoke<number>("start_http_proxy", { remoteHost: host });
|
|
const origin = `http://127.0.0.1:${port}`;
|
|
origins.set(host, origin);
|
|
log.debug("tunnel ready", { host, origin });
|
|
return origin;
|
|
})();
|
|
|
|
pending.set(host, start);
|
|
try {
|
|
return await start;
|
|
} finally {
|
|
pending.delete(host);
|
|
}
|
|
}
|
|
|
|
/** Stop the tunnel for `host` and drop its cached origin (best-effort). */
|
|
export async function stopHttpProxy(host: string): Promise<void> {
|
|
origins.delete(host);
|
|
pending.delete(host);
|
|
try {
|
|
await invoke("stop_http_proxy", { remoteHost: host });
|
|
} catch (err) {
|
|
log.debug("stop_http_proxy failed (ignored)", { host, error: String(err) });
|
|
}
|
|
}
|