JM Tech Power

jm-tech-power-official/Flash Download

A desktop download manager that opens up to 32 connections against one file and can send each of them out a different proxy route.

Public Uncategorized
Clone jm-tech-power-official/flash-download.jmproj
test/secrets.test.js
'use strict';

// Proxy credentials must never reach state.json as plaintext. These tests drive
// Manager with a fake codec (so no Electron/keyring is needed) and assert on the
// actual bytes written to disk.

const assert = require('assert');
const fs = require('fs/promises');
const os = require('os');
const path = require('path');
const { Manager } = require('../src/main/engine/manager');
const { SecretStore } = require('../src/main/engine/secrets');

const tests = [];
const test = (name, fn) => tests.push([name, fn]);

// A reversible "encryption" standing in for safeStorage: good enough to prove
// the split between state.json and the encrypted store, and to prove that what
// lands in credentials.enc is not the readable password.
function fakeCodec({ available = true } = {}) {
  return {
    available: () => available,
    encrypt: (s) => 'enc:' + Buffer.from(s, 'utf8').toString('base64'),
    decrypt: (b) => {
      if (!String(b).startsWith('enc:')) throw new Error('bad blob');
      return Buffer.from(String(b).slice(4), 'base64').toString('utf8');
    },
  };
}

const readJson = async (p) => JSON.parse(await fs.readFile(p, 'utf8'));
const exists = (p) => fs.access(p).then(() => true, () => false);

async function makeManager(dir, codecOpts) {
  const secrets = new SecretStore({ filePath: path.join(dir, 'credentials.enc'), codec: fakeCodec(codecOpts) });
  const m = new Manager({ stateDir: dir, defaultDownloadDir: dir, secrets });
  await m.load();
  return m;
}

const SECRET = 'hunter2-s3cr3t';
const USER = 'alice';

// ---------------------------------------------------------------------------

test('a proxy password never appears in state.json', async () => {
  const dir = await fs.mkdtemp(path.join(os.tmpdir(), 'fd-sec-'));
  const m = await makeManager(dir);
  await m.addRoute({ label: 'p1', type: 'http', host: '127.0.0.1', port: 8080, username: USER, password: SECRET });
  await m.shutdown();

  const rawState = await fs.readFile(path.join(dir, 'state.json'), 'utf8');
  assert.ok(!rawState.includes(SECRET), 'password must not be in state.json');
  assert.ok(!rawState.includes(USER), 'username must not be in state.json');

  const state = JSON.parse(rawState);
  const route = state.routes.find((r) => r.label === 'p1');
  assert.ok(route, 'route still recorded');
  assert.strictEqual(route.username, undefined, 'no username key in state.json');
  assert.strictEqual(route.password, undefined, 'no password key in state.json');

  await fs.rm(dir, { recursive: true, force: true });
});

test('credentials.enc holds ciphertext, not the readable secret', async () => {
  const dir = await fs.mkdtemp(path.join(os.tmpdir(), 'fd-sec-'));
  const m = await makeManager(dir);
  await m.addRoute({ label: 'p1', type: 'http', host: '127.0.0.1', port: 8080, username: USER, password: SECRET });
  await m.shutdown();

  const encPath = path.join(dir, 'credentials.enc');
  assert.ok(await exists(encPath), 'credentials.enc written');
  const raw = await fs.readFile(encPath, 'utf8');
  assert.ok(!raw.includes(SECRET), 'raw secret must not be in the encrypted file');
  const blob = Object.values((await readJson(encPath)).secrets)[0];
  assert.ok(blob.startsWith('enc:'), 'value is stored as a codec blob');

  await fs.rm(dir, { recursive: true, force: true });
});

test('credentials survive a restart and reach the proxy agent', async () => {
  const dir = await fs.mkdtemp(path.join(os.tmpdir(), 'fd-sec-'));
  const m1 = await makeManager(dir);
  const id = await m1.addRoute({ label: 'p1', type: 'socks5', host: '10.0.0.1', port: 1080, username: USER, password: SECRET });
  await m1.shutdown();

  const m2 = await makeManager(dir);               // fresh process
  const route = m2.routes.find((r) => r.id === id);
  assert.strictEqual(route.username, USER, 'username restored in memory');
  assert.strictEqual(route.password, SECRET, 'password restored in memory');

  // snapshot (what the renderer sees) exposes only presence, never the secret.
  const snap = m2.snapshot().routes.find((r) => r.id === id);
  assert.strictEqual(snap.hasAuth, true);
  assert.ok(!('password' in snap) && !('username' in snap), 'snapshot hides credentials');
  await m2.shutdown();
  await fs.rm(dir, { recursive: true, force: true });
});

test('a legacy plaintext state.json is migrated and rewritten clean', async () => {
  const dir = await fs.mkdtemp(path.join(os.tmpdir(), 'fd-sec-'));
  // Simulate an old install: credentials inline in state.json, no encrypted file.
  const legacy = {
    v: 1,
    settings: {},
    routes: [
      { id: 'direct', label: 'direct', type: 'direct', enabled: true, health: 'up' },
      { id: 'old1', label: 'legacy', type: 'http', host: '1.2.3.4', port: 3128, username: USER, password: SECRET, enabled: true, health: 'up' },
    ],
    downloads: [],
  };
  await fs.writeFile(path.join(dir, 'state.json'), JSON.stringify(legacy, null, 2));

  const m = await makeManager(dir);                 // load() migrates
  const rawState = await fs.readFile(path.join(dir, 'state.json'), 'utf8');
  assert.ok(!rawState.includes(SECRET), 'legacy password scrubbed from state.json');
  assert.ok(await exists(path.join(dir, 'credentials.enc')), 'migrated into encrypted store');

  const route = m.routes.find((r) => r.id === 'old1');
  assert.strictEqual(route.password, SECRET, 'credential still usable in memory');
  await m.shutdown();

  // And it is readable again next launch.
  const m2 = await makeManager(dir);
  assert.strictEqual(m2.routes.find((r) => r.id === 'old1').password, SECRET);
  await m2.shutdown();
  await fs.rm(dir, { recursive: true, force: true });
});

test('with no keyring, nothing is written as plaintext and nothing persists', async () => {
  const dir = await fs.mkdtemp(path.join(os.tmpdir(), 'fd-sec-'));
  const m = await makeManager(dir, { available: false });
  let warned = false;
  m.on('notice', (n) => { if (/password/i.test(n.title)) warned = true; });

  await m.addRoute({ label: 'p1', type: 'http', host: '127.0.0.1', port: 8080, username: USER, password: SECRET });
  // Usable this session...
  assert.strictEqual(m.routes.find((r) => r.label === 'p1').password, SECRET);
  assert.ok(warned, 'user is warned that the password will not be saved');
  await m.shutdown();

  const rawState = await fs.readFile(path.join(dir, 'state.json'), 'utf8');
  assert.ok(!rawState.includes(SECRET), 'no plaintext in state.json even without a keyring');
  assert.ok(!(await exists(path.join(dir, 'credentials.enc'))), 'no encrypted file when encryption is unavailable');

  // Next launch: the credential is gone (session-only), as warned.
  const m2 = await makeManager(dir, { available: false });
  const route = m2.routes.find((r) => r.label === 'p1');
  assert.ok(route, 'route config itself still persists');
  assert.ok(!route.password, 'password did not persist');
  await m2.shutdown();
  await fs.rm(dir, { recursive: true, force: true });
});

test('removing a route drops its stored secret', async () => {
  const dir = await fs.mkdtemp(path.join(os.tmpdir(), 'fd-sec-'));
  const m = await makeManager(dir);
  const id = await m.addRoute({ label: 'p1', type: 'http', host: '127.0.0.1', port: 8080, username: USER, password: SECRET });
  await m.removeRoute(id);
  await m.shutdown();

  const enc = await readJson(path.join(dir, 'credentials.enc'));
  assert.deepStrictEqual(enc.secrets, {}, 'removed route leaves no secret behind');
  await fs.rm(dir, { recursive: true, force: true });
});

// ---------------------------------------------------------------------------

(async () => {
  let failed = 0;
  for (const [name, fn] of tests) {
    const t0 = Date.now();
    try {
      await fn();
      console.log(`  \x1b[32mpass\x1b[0m  ${name} \x1b[90m(${Date.now() - t0}ms)\x1b[0m`);
    } catch (err) {
      failed += 1;
      console.log(`  \x1b[31mFAIL\x1b[0m  ${name}\n        ${err.message}`);
    }
  }
  console.log(failed ? `\n${failed} of ${tests.length} failed` : `\n${tests.length} passed`);
  process.exit(failed ? 1 : 0);
})();