Use host preparation for TURN connection settings

This commit is contained in:
vorotamoroz
2026-09-16 03:43:09 +00:00
parent a565070809
commit 11a07b26af
33 changed files with 654 additions and 961 deletions
+84 -16
View File
@@ -1,26 +1,55 @@
import { describe, expect, it } from "vitest";
import { DEFAULT_SETTINGS } from "@vrtmrz/livesync-commonlib/compat/common/types";
import {
DEFAULT_SETTINGS,
REMOTE_P2P,
type ObsidianLiveSyncSettings,
} from "@vrtmrz/livesync-commonlib/compat/common/types";
import {
SettingService,
type SettingServiceDependencies,
} from "@vrtmrz/livesync-commonlib/compat/services/base/SettingService";
import { ServiceContext } from "@vrtmrz/livesync-commonlib/compat/services/base/ServiceBase";
import { ConnectionStringParser } from "@vrtmrz/livesync-commonlib/compat/common/ConnectionString";
import {
hasManagedTurnSettings,
omitManagedTurnProfilesFromMarkdown,
preserveManagedTurnProfilesOnMarkdownImport,
redactTurnSourceForReport,
redactTurnSettingsForReport,
} from "./turnSettingsPrivacy";
class MemorySettingService extends SettingService {
readonly items = new Map<string, string>();
saved?: ObsidianLiveSyncSettings;
protected setItem(key: string, value: string) {
this.items.set(key, value);
}
protected getItem(key: string) {
return this.items.get(key) ?? "";
}
protected deleteItem(key: string) {
this.items.delete(key);
}
protected saveData(settings: ObsidianLiveSyncSettings) {
this.saved = structuredClone(settings);
return Promise.resolve();
}
protected loadData() {
return Promise.resolve(this.saved);
}
}
function configuredSettings() {
return {
...DEFAULT_SETTINGS,
P2P_iceServerSource: {
version: 1,
id: "cloudflare",
configuration: { turnKeyId: "private-key-id", apiToken: "private-token" },
},
P2P_managedType: "CF",
P2P_managedId: "private-key-id",
P2P_managedToken: "private-token",
remoteConfigurations: {
managed: {
id: "managed",
name: "Managed TURN",
isEncrypted: false,
uri: "sls+p2p://room?source=private-token",
uri: "sls+p2p://room?managedType=CF&managedId=private-key-id&token=private-token",
},
},
activeConfigurationId: "central",
@@ -29,17 +58,56 @@ function configuredSettings() {
}
describe("managed TURN settings privacy", () => {
it("redacts all opaque source fields, including unknown integrations", () => {
it("preserves the active managed room through Markdown import, save, and reload", async () => {
const current = {
...configuredSettings(),
remoteType: REMOTE_P2P,
activeConfigurationId: "managed",
P2P_roomID: "local-room",
P2P_relays: "wss://local-relay.example.test",
P2P_passphrase: "local-passphrase",
};
const originalURI = ConnectionStringParser.serialize({ type: "p2p", settings: current });
current.remoteConfigurations.managed.uri = originalURI;
const service = new MemorySettingService(new ServiceContext(), {
APIService: {
getSystemVaultName: () => "test-vault",
getAppID: () => "test-app",
addLog: () => undefined,
confirm: { askString: async () => "" },
} as unknown as SettingServiceDependencies["APIService"],
});
service.settings = structuredClone(current);
const incoming: Partial<ObsidianLiveSyncSettings> = {
P2P_roomID: "imported-room",
P2P_relays: "wss://imported-relay.example.test",
P2P_passphrase: "imported-passphrase",
};
const merged = { ...structuredClone(DEFAULT_SETTINGS), ...incoming };
preserveManagedTurnProfilesOnMarkdownImport(incoming, current, merged);
await service.applyExternalSettings(merged, true);
const saved = service.saved!.remoteConfigurations.managed;
const uri = saved.isEncrypted ? await service.decryptConfigurationItem(saved.uri, "*") : saved.uri;
expect(uri).toBe(originalURI);
expect(service.settings.P2P_roomID).toBe("local-room");
await service.loadSettings();
expect(service.settings.P2P_roomID).toBe("local-room");
});
it("redacts provider fields and issued credentials, including unknown integrations", () => {
const settings = configuredSettings();
settings.P2P_iceServerSource.id = "private-token";
redactTurnSourceForReport(settings);
expect(JSON.stringify(settings.P2P_iceServerSource)).not.toMatch(/private-token|private-key-id/);
expect(settings.P2P_iceServerSource.configuration).toEqual({ redacted: true });
settings.P2P_managedType = "private-token";
redactTurnSettingsForReport(settings);
expect([settings.P2P_managedType, settings.P2P_managedId, settings.P2P_managedToken]).toEqual([
"redacted",
"redacted",
"redacted",
]);
});
it("omits the whole managed profile group from Markdown, including inactive sources", () => {
const settings = configuredSettings();
settings.P2P_iceServerSource.id = "manual";
settings.P2P_managedType = "";
expect(hasManagedTurnSettings(settings)).toBe(true);
omitManagedTurnProfilesFromMarkdown(settings);
expect(JSON.stringify(settings)).not.toMatch(/private-token|private-key-id|sls\+p2p/);
@@ -52,12 +120,12 @@ describe("managed TURN settings privacy", () => {
const current = configuredSettings();
const incoming = { ...DEFAULT_SETTINGS };
delete (incoming as Partial<typeof incoming>).remoteConfigurations;
delete (incoming as Partial<typeof incoming>).P2P_iceServerSource;
delete (incoming as Partial<typeof incoming>).P2P_managedType;
const merged = { ...DEFAULT_SETTINGS, ...incoming };
preserveManagedTurnProfilesOnMarkdownImport(incoming, current, merged);
expect(merged.remoteConfigurations).toEqual(current.remoteConfigurations);
expect(merged.remoteConfigurations).not.toBe(current.remoteConfigurations);
expect(merged.P2P_iceServerSource).toEqual(current.P2P_iceServerSource);
expect(merged.P2P_managedToken).toEqual(current.P2P_managedToken);
expect(merged.activeConfigurationId).toBe("central");
expect(merged.P2P_ActiveRemoteConfigurationId).toBe("managed");
});