Files
NetMesh/electron/bridges/appLockSettingsStore.test.cjs

182 lines
5.5 KiB
JavaScript
Raw Normal View History

const test = require("node:test");
const assert = require("node:assert/strict");
const {
createAppLockPasswordVerifier,
createAppLockSettingsStore,
canLockFromSettings,
shouldLockOnBackgroundHide,
verifyAppLockPassword,
} = require("./appLockSettingsStore.cjs");
const fs = require("node:fs");
const path = require("node:path");
const settingsStoreSource = fs.readFileSync(path.join(__dirname, "appLockSettingsStore.cjs"), "utf8");
test("password hashing uses the asynchronous crypto API", () => {
assert.doesNotMatch(settingsStoreSource, /pbkdf2Sync/);
assert.match(settingsStoreSource, /\bpbkdf2\b/);
});
const VALID_VERIFIER = {
version: 1,
algorithm: "PBKDF2-SHA256",
iterations: 210000,
salt: Buffer.alloc(16, 1).toString("base64"),
hash: Buffer.alloc(32, 2).toString("base64"),
};
test("settings store loads disabled config when no persisted file exists", async () => {
const store = createAppLockSettingsStore({
filePath: "/tmp/app-lock-settings.json",
readFile: async () => {
const err = new Error("ENOENT");
err.code = "ENOENT";
throw err;
},
writeFile: async () => {},
});
const settings = await store.load();
assert.deepEqual(settings, {
enabled: false,
timeoutMinutes: 15,
systemUnlockEnabled: false,
systemUnlockAutoPromptEnabled: false,
passwordVerifier: null,
});
assert.deepEqual(store.getSnapshot(), settings);
});
test("settings store normalizes malformed persisted config to disabled defaults", async () => {
const store = createAppLockSettingsStore({
filePath: "/tmp/app-lock-settings.json",
readFile: async () =>
JSON.stringify({
enabled: true,
timeoutMinutes: 999,
passwordVerifier: { hash: "invalid" },
}),
writeFile: async () => {},
});
const settings = await store.load();
assert.deepEqual(settings, {
enabled: false,
timeoutMinutes: 15,
systemUnlockEnabled: false,
systemUnlockAutoPromptEnabled: false,
passwordVerifier: null,
});
});
test("settings store saves normalized settings and updates snapshot", async () => {
let writeCall = null;
const store = createAppLockSettingsStore({
filePath: "/tmp/app-lock-settings.json",
readFile: async () => {
const err = new Error("ENOENT");
err.code = "ENOENT";
throw err;
},
writeFile: async (filePath, content, options) => {
writeCall = { filePath, content, options };
},
});
const saved = await store.save({
enabled: true,
timeoutMinutes: 0,
systemUnlockEnabled: true,
systemUnlockAutoPromptEnabled: true,
passwordVerifier: VALID_VERIFIER,
});
assert.deepEqual(saved, {
enabled: true,
timeoutMinutes: 0,
systemUnlockEnabled: true,
systemUnlockAutoPromptEnabled: true,
passwordVerifier: VALID_VERIFIER,
});
assert.deepEqual(store.getSnapshot(), saved);
assert.deepEqual(writeCall, {
filePath: "/tmp/app-lock-settings.json",
content: `${JSON.stringify(saved, null, 2)}\n`,
options: { mode: 0o600 },
});
});
test("settings store clears system unlock when no valid verifier exists", async () => {
const store = createAppLockSettingsStore({
filePath: "/tmp/app-lock-settings.json",
readFile: async () =>
JSON.stringify({
enabled: true,
timeoutMinutes: 5,
systemUnlockEnabled: true,
systemUnlockAutoPromptEnabled: true,
passwordVerifier: null,
}),
writeFile: async () => {},
});
const settings = await store.load();
assert.deepEqual(settings, {
enabled: false,
timeoutMinutes: 5,
systemUnlockEnabled: false,
systemUnlockAutoPromptEnabled: false,
passwordVerifier: null,
});
});
test("canLockFromSettings requires enabled and a verifier", async () => {
assert.equal(canLockFromSettings({ enabled: false, passwordVerifier: VALID_VERIFIER }), false);
assert.equal(canLockFromSettings({ enabled: true, passwordVerifier: null }), false);
assert.equal(canLockFromSettings({ enabled: true, passwordVerifier: { hash: "x" } }), false);
assert.equal(canLockFromSettings({ enabled: true, passwordVerifier: VALID_VERIFIER }), true);
});
test("shouldLockOnBackgroundHide requires an automatic timeout", () => {
assert.equal(shouldLockOnBackgroundHide({
enabled: true,
timeoutMinutes: 0,
passwordVerifier: VALID_VERIFIER,
}), false);
assert.equal(shouldLockOnBackgroundHide({
enabled: true,
timeoutMinutes: 5,
passwordVerifier: VALID_VERIFIER,
}), true);
assert.equal(shouldLockOnBackgroundHide({
enabled: false,
timeoutMinutes: 5,
passwordVerifier: VALID_VERIFIER,
}), false);
});
test("createAppLockPasswordVerifier stores a verifier that verifyAppLockPassword accepts", async () => {
const verifier = await createAppLockPasswordVerifier("correct horse battery staple");
assert.equal(verifier.version, 1);
assert.equal(verifier.algorithm, "PBKDF2-SHA256");
assert.ok(verifier.iterations >= 100000);
assert.notEqual(verifier.salt, "");
assert.notEqual(verifier.hash, "");
assert.equal(await verifyAppLockPassword("correct horse battery staple", verifier), true);
assert.equal(await verifyAppLockPassword("wrong password", verifier), false);
});
test("createAppLockPasswordVerifier preserves a whitespace-only password", async () => {
const verifier = await createAppLockPasswordVerifier(" ");
assert.equal(await verifyAppLockPassword(" ", verifier), true);
assert.equal(await verifyAppLockPassword("", verifier), false);
await assert.rejects(
() => createAppLockPasswordVerifier(""),
/App lock password is required/,
);
});