Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
77 changes: 0 additions & 77 deletions apps/desktop/src/linuxSecretStorage.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import { describe, expect, it } from "vite-plus/test";
import {
normalizeLinuxPasswordStorePreference,
resolveLinuxPasswordStoreSwitch,
resolveLinuxSecretStorageUnavailableMessage,
} from "./linuxSecretStorage.ts";

const autoSwitch = (env: NodeJS.ProcessEnv) =>
Expand Down Expand Up @@ -124,80 +123,4 @@ describe("linuxSecretStorage", () => {
}),
).toBe("gnome-libsecret");
});

it("uses GNOME Keyring remediation for libsecret and unknown backends", () => {
expect(
resolveLinuxSecretStorageUnavailableMessage({
configuredPreference: "auto",
selectedBackend: "gnome_libsecret",
env: { XDG_CURRENT_DESKTOP: "niri" },
}),
).toContain("GNOME Keyring");
});

it("prefers explicit libsecret selection over KDE desktop heuristics", () => {
expect(
resolveLinuxSecretStorageUnavailableMessage({
configuredPreference: "gnome-libsecret",
selectedBackend: "unknown",
env: { XDG_CURRENT_DESKTOP: "KDE" },
}),
).toContain("GNOME Keyring");
expect(
resolveLinuxSecretStorageUnavailableMessage({
configuredPreference: "auto",
selectedBackend: "gnome_libsecret",
env: { XDG_CURRENT_DESKTOP: "KDE" },
}),
).toContain("GNOME Keyring");
});

it("prefers explicit KWallet preference over selected gnome-libsecret backend", () => {
expect(
resolveLinuxSecretStorageUnavailableMessage({
configuredPreference: "kwallet6",
selectedBackend: "gnome_libsecret",
env: { XDG_CURRENT_DESKTOP: "niri" },
}),
).toContain("KWallet");
expect(
resolveLinuxSecretStorageUnavailableMessage({
configuredPreference: "kwallet",
selectedBackend: "gnome-libsecret",
env: {},
}),
).toContain("KWallet");
});

it("uses KWallet remediation wording for KDE-looking sessions", () => {
expect(
resolveLinuxSecretStorageUnavailableMessage({
configuredPreference: "auto",
selectedBackend: "kwallet6",
env: {},
}),
).toContain("KWallet");
expect(
resolveLinuxSecretStorageUnavailableMessage({
configuredPreference: "auto",
selectedBackend: "unknown",
env: { XDG_CURRENT_DESKTOP: "KDE" },
}),
).toContain("KWallet");
expect(
resolveLinuxSecretStorageUnavailableMessage({
configuredPreference: "auto",
selectedBackend: "unknown",
env: { DESKTOP_SESSION: "plasmawayland" },
}),
).toContain("KWallet");
// A desktop name outranks a bare KDE marker when choosing the wording.
expect(
resolveLinuxSecretStorageUnavailableMessage({
configuredPreference: "auto",
selectedBackend: "unknown",
env: { GDMSESSION: "gnome", KDE_FULL_SESSION: "true" },
}),
).toContain("GNOME Keyring");
});
});
99 changes: 0 additions & 99 deletions apps/desktop/src/linuxSecretStorage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,6 @@ const ELECTRON_KDE_DESKTOP = "KDE";
// Chromium recognizes LXQt and still selects basic text for it, so it does need a forced backend.
const ELECTRON_UNPROTECTED_DESKTOPS = new Set(["LXQt"]);

const KDE_NAME_PREFIXES = ["kde", "plasma"];
const NEGATIVE_FLAG_VALUES = new Set(["0", "false", "no", "off"]);

export function normalizeLinuxPasswordStorePreference(
value: unknown,
): LinuxPasswordStorePreference {
Expand Down Expand Up @@ -77,102 +74,6 @@ function electronSelectsProtectedBackend(env: NodeJS.ProcessEnv): boolean {
return false;
}

export function resolveLinuxSecretStorageUnavailableMessage(input: {
readonly configuredPreference: LinuxPasswordStorePreference;
readonly selectedBackend: string | null;
readonly env: NodeJS.ProcessEnv;
}): string {
if (input.configuredPreference === "gnome-libsecret") {
return getGnomeKeyringRemediationMessage();
}

if (
input.configuredPreference === "kwallet" ||
input.configuredPreference === "kwallet5" ||
input.configuredPreference === "kwallet6"
) {
return getKWalletRemediationMessage();
}

const backend = normalizeSelectedStorageBackend(input.selectedBackend);
if (backend === "gnome-libsecret") {
return getGnomeKeyringRemediationMessage();
}

if (
backend === "kwallet" ||
backend === "kwallet5" ||
backend === "kwallet6" ||
looksLikeKdeSession(input.env)
) {
return getKWalletRemediationMessage();
}

return getGnomeKeyringRemediationMessage();
}

function getGnomeKeyringRemediationMessage(): string {
return "T3 Code could not access GNOME Keyring to save this environment credential. Install and start GNOME Keyring, then restart T3 Code.";
}

function getKWalletRemediationMessage(): string {
return "T3 Code could not access KWallet to save this environment credential. Enable the KDE wallet subsystem in System Settings, then restart T3 Code.";
}

// Advisory only: this picks between the GNOME Keyring and KWallet wording in the failure notice. It
// never decides which backend to select, so a loose match costs a user slightly wrong instructions
// rather than an unprotected credential store.
function looksLikeKdeSession(env: NodeJS.ProcessEnv): boolean {
const currentDesktopNames = nonEmptyDesktopNames(env.XDG_CURRENT_DESKTOP);
if (currentDesktopNames.length > 0) {
return currentDesktopNames.some(isKdeDesktopName);
}

const legacyNames = legacyDesktopNames(env);
if (legacyNames.length > 0) {
return legacyNames.some(isKdeDesktopName);
}

return isSet(env.KDE_SESSION_VERSION) || isAffirmativeFlag(env.KDE_FULL_SESSION);
}

function isKdeDesktopName(name: string): boolean {
return KDE_NAME_PREFIXES.some((prefix) => name.startsWith(prefix));
}

function legacyDesktopNames(env: NodeJS.ProcessEnv): string[] {
return [env.XDG_SESSION_DESKTOP, env.DESKTOP_SESSION, env.GDMSESSION].flatMap((entry) => {
const normalized = normalizeDesktopName(entry);
return normalized ? [normalized] : [];
});
}

function nonEmptyDesktopNames(value: string | undefined): string[] {
return splitDesktopNameList(value).flatMap((entry) => {
const normalized = normalizeDesktopName(entry);
return normalized ? [normalized] : [];
});
}

function isSet(value: string | undefined): boolean {
return Boolean(value?.trim());
}

function isAffirmativeFlag(value: string | undefined): boolean {
const normalized = value?.trim().toLowerCase();
return normalized ? !NEGATIVE_FLAG_VALUES.has(normalized) : false;
}

function splitDesktopNameList(value: string | undefined): string[] {
return value?.split(":") ?? [];
}

function normalizeDesktopName(value: string | undefined): string | null {
const normalized = value?.trim().toLowerCase();
return normalized && normalized.length > 0 ? normalized : null;
}

function normalizeSelectedStorageBackend(value: string | null): string | null {
const normalized = value?.trim().toLowerCase().replace(/_/gu, "-");
return normalized && normalized.length > 0 ? normalized : null;
}
Loading