Files
Multi-Hoster-Upload/tests/secret-store.test.js
T
Sucukdeluxe bf33ab3e99
CI / verify (push) Canceled after 0s
fix: defer secure credential loading until ready
Load encrypted account credentials only after Electron reaches its ready state so Windows DPAPI is available during startup. Retry transient secure-storage discovery instead of caching an unavailable result for the process lifetime. Add hidden two-process DPAPI regression coverage for the real renderer IPC path and bump the public version to 2.1.40.
2026-08-27 16:07:21 +02:00

159 lines
5.6 KiB
JavaScript

const test = require('node:test');
const assert = require('node:assert/strict');
const Module = require('node:module');
const secretStorePath = require.resolve('../lib/secret-store');
function withSecretStore(safeStorage, action) {
const originalLoad = Module._load;
Module._load = function load(request, parent, isMain) {
if (request === 'electron') return { safeStorage };
return originalLoad.call(this, request, parent, isMain);
};
delete require.cache[secretStorePath];
try {
return action(require(secretStorePath));
} finally {
Module._load = originalLoad;
delete require.cache[secretStorePath];
}
}
function availableSafeStorage(overrides = {}) {
return {
isEncryptionAvailable: () => true,
encryptString: value => Buffer.from(`protected:${value}`),
decryptString: value => value.toString().replace(/^protected:/, ''),
...overrides
};
}
test('encrypts and decrypts fields when secure storage is available', () => {
withSecretStore(availableSafeStorage(), secretStore => {
const encrypted = secretStore.encryptField('secret');
assert.match(encrypted, /^enc:v1:/);
assert.equal(secretStore.decryptField(encrypted), 'secret');
});
});
test('recognizes only canonical enc:v1 envelopes as encrypted', () => {
withSecretStore(availableSafeStorage(), secretStore => {
const canonical = `enc:v1:${Buffer.from('protected:secret').toString('base64')}`;
assert.equal(secretStore.isEncrypted(canonical), true);
assert.equal(secretStore.isEncrypted('secret'), false);
assert.equal(secretStore.isEncrypted(canonical.replace('enc:v1:', 'enc:v2:')), false);
assert.equal(secretStore.isEncrypted(canonical.replace(/=+$/u, '')), false);
assert.equal(secretStore.isEncrypted(`${canonical}\n`), false);
});
});
test('refuses plaintext storage by default when secure storage is unavailable', () => {
withSecretStore(null, secretStore => {
assert.throws(
() => secretStore.encryptField('secret'),
error => error instanceof secretStore.SecretStoreError
&& error.code === 'SECRET_STORE_UNAVAILABLE'
);
});
});
test('never allows plaintext storage when secure storage is unavailable', () => {
withSecretStore(null, secretStore => {
assert.throws(
() => secretStore.encryptField('secret', { allowPlaintext: true }),
error => error instanceof secretStore.SecretStoreError
&& error.code === 'SECRET_STORE_UNAVAILABLE'
);
const config = { hosters: { example: [{ password: 'secret' }] } };
assert.throws(
() => secretStore.encryptCredentials(config, { allowPlaintext: true }),
error => error instanceof secretStore.SecretStoreError
&& error.code === 'SECRET_STORE_UNAVAILABLE'
);
});
});
test('refuses plaintext storage by default when encryption fails', () => {
const failure = new Error('encryption failed');
withSecretStore(availableSafeStorage({ encryptString: () => { throw failure; } }), secretStore => {
assert.throws(
() => secretStore.encryptField('secret'),
error => error instanceof secretStore.SecretStoreError
&& error.code === 'SECRET_STORE_ENCRYPT_FAILED'
&& error.cause === failure
);
assert.throws(
() => secretStore.encryptField('secret', { allowPlaintext: true }),
error => error instanceof secretStore.SecretStoreError
&& error.code === 'SECRET_STORE_ENCRYPT_FAILED'
&& error.cause === failure
);
});
});
test('throws an identifiable error for encrypted values without secure storage', () => {
withSecretStore(null, secretStore => {
assert.throws(
() => secretStore.decryptField('enc:v1:cHJvdGVjdGVkOnNlY3JldA=='),
error => error instanceof secretStore.SecretStoreError
&& error.code === 'SECRET_STORE_UNAVAILABLE'
);
});
});
test('retries secure storage discovery after transient unavailability', () => {
let available = false;
let checks = 0;
const encrypted = `enc:v1:${Buffer.from('protected:secret').toString('base64')}`;
withSecretStore(availableSafeStorage({
isEncryptionAvailable: () => {
checks++;
return available;
}
}), secretStore => {
assert.throws(
() => secretStore.decryptField(encrypted),
error => error instanceof secretStore.SecretStoreError
&& error.code === 'SECRET_STORE_UNAVAILABLE'
);
available = true;
assert.equal(secretStore.decryptField(encrypted), 'secret');
assert.equal(checks, 2);
});
});
test('throws an identifiable error when decryption fails', () => {
const failure = new Error('decryption failed');
withSecretStore(availableSafeStorage({ decryptString: () => { throw failure; } }), secretStore => {
assert.throws(
() => secretStore.decryptField('enc:v1:aW52YWxpZA=='),
error => error instanceof secretStore.SecretStoreError
&& error.code === 'SECRET_STORE_DECRYPT_FAILED'
&& error.cause === failure
);
});
});
test('rejects malformed enc:v1 values instead of treating them as legacy plaintext', () => {
let decryptCalls = 0;
withSecretStore(availableSafeStorage({
decryptString: () => {
decryptCalls++;
return 'unexpected';
}
}), secretStore => {
assert.throws(
() => secretStore.decryptField('enc:v1:YWJjZA'),
error => error instanceof secretStore.SecretStoreError
&& error.code === 'SECRET_STORE_DECRYPT_FAILED'
);
assert.equal(decryptCalls, 0);
});
});
test('keeps legacy plaintext values readable without secure storage', () => {
withSecretStore(null, secretStore => {
assert.equal(secretStore.decryptField('legacy-secret'), 'legacy-secret');
});
});