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
65 lines
2.5 KiB
TypeScript
65 lines
2.5 KiB
TypeScript
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
|
|
const { invokeMock } = vi.hoisted(() => ({ invokeMock: vi.fn() }));
|
|
|
|
vi.mock("@tauri-apps/api/core", () => ({ invoke: invokeMock }));
|
|
vi.mock("@lib/logger", () => ({
|
|
createLogger: () => ({ debug: vi.fn(), info: vi.fn(), warn: vi.fn(), error: vi.fn() }),
|
|
}));
|
|
|
|
import { ensureHttpProxy, stopHttpProxy } from "../../src/lib/httpProxy";
|
|
|
|
describe("ensureHttpProxy", () => {
|
|
beforeEach(() => {
|
|
invokeMock.mockReset();
|
|
// Clear per-host cache between tests by stopping any previously started host.
|
|
return stopHttpProxy("cache.example:8443").then(() => invokeMock.mockReset());
|
|
});
|
|
|
|
it("starts a tunnel and returns the loopback origin", async () => {
|
|
invokeMock.mockResolvedValue(51234);
|
|
const origin = await ensureHttpProxy("host-a.example:8443");
|
|
expect(origin).toBe("http://127.0.0.1:51234");
|
|
expect(invokeMock).toHaveBeenCalledWith("start_http_proxy", {
|
|
remoteHost: "host-a.example:8443",
|
|
});
|
|
});
|
|
|
|
it("caches the origin per host (one start per host)", async () => {
|
|
invokeMock.mockResolvedValue(40000);
|
|
const a = await ensureHttpProxy("host-b.example:8443");
|
|
const b = await ensureHttpProxy("host-b.example:8443");
|
|
expect(a).toBe(b);
|
|
expect(invokeMock).toHaveBeenCalledTimes(1);
|
|
});
|
|
|
|
it("de-duplicates concurrent starts for the same host", async () => {
|
|
let resolvePort: (p: number) => void = () => {};
|
|
invokeMock.mockReturnValue(new Promise<number>((r) => (resolvePort = r)));
|
|
const p1 = ensureHttpProxy("host-c.example:8443");
|
|
const p2 = ensureHttpProxy("host-c.example:8443");
|
|
resolvePort(45000);
|
|
const [o1, o2] = await Promise.all([p1, p2]);
|
|
expect(o1).toBe("http://127.0.0.1:45000");
|
|
expect(o2).toBe("http://127.0.0.1:45000");
|
|
expect(invokeMock).toHaveBeenCalledTimes(1);
|
|
});
|
|
|
|
it("stopHttpProxy invokes stop and drops the cache so a restart re-invokes", async () => {
|
|
invokeMock.mockResolvedValue(46000);
|
|
await ensureHttpProxy("host-d.example:8443");
|
|
await stopHttpProxy("host-d.example:8443");
|
|
expect(invokeMock).toHaveBeenCalledWith("stop_http_proxy", {
|
|
remoteHost: "host-d.example:8443",
|
|
});
|
|
|
|
invokeMock.mockReset();
|
|
invokeMock.mockResolvedValue(46001);
|
|
const origin = await ensureHttpProxy("host-d.example:8443");
|
|
expect(origin).toBe("http://127.0.0.1:46001");
|
|
expect(invokeMock).toHaveBeenCalledWith("start_http_proxy", {
|
|
remoteHost: "host-d.example:8443",
|
|
});
|
|
});
|
|
});
|