138 lines
4.7 KiB
TypeScript
138 lines
4.7 KiB
TypeScript
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
import { DESKTOP_SERVER_URL_KEY } from "./desktopServerConfig";
|
|
import {
|
|
clearLoginCredential,
|
|
getSavedLoginCredential,
|
|
saveLoginCredential,
|
|
} from "./savedLoginCredentials";
|
|
|
|
const invokeMock = vi.hoisted(() => vi.fn());
|
|
|
|
vi.mock("@tauri-apps/api/core", () => ({
|
|
invoke: invokeMock,
|
|
}));
|
|
|
|
const SERVER_ORIGIN = "https://ctms.example.com/";
|
|
|
|
const createStorage = (): Storage => {
|
|
const data = new Map<string, string>();
|
|
return {
|
|
get length() {
|
|
return data.size;
|
|
},
|
|
clear: () => data.clear(),
|
|
getItem: (key) => data.get(key) ?? null,
|
|
key: (index) => Array.from(data.keys())[index] ?? null,
|
|
removeItem: (key) => data.delete(key),
|
|
setItem: (key, value) => {
|
|
data.set(key, String(value));
|
|
},
|
|
};
|
|
};
|
|
|
|
describe("saved login credentials", () => {
|
|
beforeEach(() => {
|
|
vi.useFakeTimers();
|
|
vi.setSystemTime(new Date("2026-07-08T00:00:00.000Z"));
|
|
Object.defineProperty(window, "localStorage", { value: createStorage(), configurable: true });
|
|
localStorage.clear();
|
|
invokeMock.mockReset();
|
|
Reflect.deleteProperty(window, "__TAURI_INTERNALS__");
|
|
Reflect.deleteProperty(window, "PasswordCredential");
|
|
Reflect.deleteProperty(navigator, "credentials");
|
|
});
|
|
|
|
afterEach(() => {
|
|
vi.useRealTimers();
|
|
localStorage.clear();
|
|
Reflect.deleteProperty(window, "__TAURI_INTERNALS__");
|
|
Reflect.deleteProperty(window, "PasswordCredential");
|
|
Reflect.deleteProperty(navigator, "credentials");
|
|
});
|
|
|
|
it("stores desktop remembered passwords in the system credential store", async () => {
|
|
Object.defineProperty(window, "__TAURI_INTERNALS__", { value: {}, configurable: true });
|
|
localStorage.setItem(DESKTOP_SERVER_URL_KEY, SERVER_ORIGIN);
|
|
let stored = "";
|
|
invokeMock.mockImplementation(async (command: string, args: any) => {
|
|
if (command === "login_credential_set") {
|
|
stored = args.credential;
|
|
return undefined;
|
|
}
|
|
if (command === "login_credential_get") return stored;
|
|
if (command === "login_credential_delete") {
|
|
stored = "";
|
|
return undefined;
|
|
}
|
|
return undefined;
|
|
});
|
|
|
|
await expect(saveLoginCredential("Admin@Example.com", "secret-password")).resolves.toBe(true);
|
|
|
|
expect(invokeMock).toHaveBeenCalledWith("login_credential_set", {
|
|
serverOrigin: SERVER_ORIGIN,
|
|
credential: expect.any(String),
|
|
});
|
|
expect(JSON.parse(stored)).toMatchObject({
|
|
version: 1,
|
|
email: "admin@example.com",
|
|
password: "secret-password",
|
|
savedAt: Date.now(),
|
|
});
|
|
await expect(getSavedLoginCredential()).resolves.toEqual({
|
|
email: "admin@example.com",
|
|
password: "secret-password",
|
|
});
|
|
await expect(clearLoginCredential()).resolves.toBe(true);
|
|
expect(invokeMock).toHaveBeenCalledWith("login_credential_delete", { serverOrigin: SERVER_ORIGIN });
|
|
});
|
|
|
|
it("deletes malformed desktop remembered credentials", async () => {
|
|
Object.defineProperty(window, "__TAURI_INTERNALS__", { value: {}, configurable: true });
|
|
localStorage.setItem(DESKTOP_SERVER_URL_KEY, SERVER_ORIGIN);
|
|
invokeMock.mockImplementation(async (command: string) => {
|
|
if (command === "login_credential_get") return JSON.stringify({ password: "missing-email" });
|
|
return undefined;
|
|
});
|
|
|
|
await expect(getSavedLoginCredential()).resolves.toBeNull();
|
|
|
|
expect(invokeMock).toHaveBeenCalledWith("login_credential_delete", { serverOrigin: SERVER_ORIGIN });
|
|
});
|
|
|
|
it("uses browser credential management without writing passwords to localStorage", async () => {
|
|
const storeMock = vi.fn();
|
|
const preventSilentAccessMock = vi.fn();
|
|
Object.defineProperty(window, "PasswordCredential", {
|
|
configurable: true,
|
|
value: class {
|
|
id: string;
|
|
password: string;
|
|
constructor(data: { id: string; password: string }) {
|
|
this.id = data.id;
|
|
this.password = data.password;
|
|
}
|
|
},
|
|
});
|
|
Object.defineProperty(navigator, "credentials", {
|
|
configurable: true,
|
|
value: {
|
|
get: vi.fn(async () => ({ id: "Browser@Example.com", password: "browser-secret" })),
|
|
store: storeMock,
|
|
preventSilentAccess: preventSilentAccessMock,
|
|
},
|
|
});
|
|
|
|
await expect(getSavedLoginCredential()).resolves.toEqual({
|
|
email: "browser@example.com",
|
|
password: "browser-secret",
|
|
});
|
|
await expect(saveLoginCredential("Browser@Example.com", "browser-secret")).resolves.toBe(true);
|
|
await expect(clearLoginCredential()).resolves.toBe(true);
|
|
|
|
expect(storeMock).toHaveBeenCalledTimes(1);
|
|
expect(preventSilentAccessMock).toHaveBeenCalledTimes(1);
|
|
expect(JSON.stringify(localStorage)).not.toContain("browser-secret");
|
|
});
|
|
});
|