feat(security): encrypt secrets and centralize app state
Store Twitch and Discord secrets as versioned safeStorage ciphertext behind explicit trusted IPC. Migrate legacy JSON transactionally into authoritative SQLite config and ordered queue repositories with rollback-safe markers and sanitized backups. Redact config exports and update renderer and release-harness contracts for secret-free config responses.
This commit is contained in:
@@ -7,7 +7,6 @@ const OFFLINE_PROXY = 'http://127.0.0.1:1';
|
||||
function buildSafeConfig(downloadsDir, overrides = {}) {
|
||||
return {
|
||||
client_id: '',
|
||||
client_secret: '',
|
||||
download_path: downloadsDir,
|
||||
streamers: [],
|
||||
theme: 'twitch',
|
||||
@@ -32,7 +31,6 @@ function buildSafeConfig(downloadsDir, overrides = {}) {
|
||||
auto_record_poll_seconds: 90,
|
||||
download_chat_replay: false,
|
||||
capture_live_chat: false,
|
||||
discord_webhook_url: '',
|
||||
discord_notify_live_start: false,
|
||||
discord_notify_live_end: false,
|
||||
discord_notify_vod_complete: false,
|
||||
@@ -50,14 +48,12 @@ function buildSafeConfig(downloadsDir, overrides = {}) {
|
||||
delete_parts_after_merge: false,
|
||||
...overrides,
|
||||
client_id: '',
|
||||
client_secret: '',
|
||||
download_path: downloadsDir,
|
||||
streamers: [],
|
||||
auto_resume_queue_on_startup: false,
|
||||
auto_record_streamers: [],
|
||||
download_chat_replay: false,
|
||||
capture_live_chat: false,
|
||||
discord_webhook_url: '',
|
||||
discord_notify_live_start: false,
|
||||
discord_notify_live_end: false,
|
||||
discord_notify_vod_complete: false,
|
||||
@@ -112,6 +108,17 @@ function writeE2eConfig(environment, overrides = {}) {
|
||||
}
|
||||
|
||||
function readE2eConfig(environment) {
|
||||
const databasePath = path.join(environment.appDataDir, 'app.db');
|
||||
if (fs.existsSync(databasePath)) {
|
||||
const Database = require('better-sqlite3');
|
||||
const database = new Database(databasePath, { readonly: true });
|
||||
try {
|
||||
const rows = database.prepare('SELECT key, value FROM config_kv').all();
|
||||
return Object.fromEntries(rows.map((row) => [row.key, JSON.parse(row.value)]));
|
||||
} finally {
|
||||
database.close();
|
||||
}
|
||||
}
|
||||
return JSON.parse(fs.readFileSync(environment.configFile, 'utf8'));
|
||||
}
|
||||
|
||||
|
||||
@@ -70,6 +70,7 @@ function inspectHelper() {
|
||||
const {
|
||||
createE2eEnvironment,
|
||||
getElectronLaunchOptions,
|
||||
readE2eConfig,
|
||||
cleanupE2eEnvironment
|
||||
} = require(HELPER_FILE);
|
||||
const environment = createE2eEnvironment('isolation-contract');
|
||||
@@ -111,8 +112,16 @@ function inspectHelper() {
|
||||
if (config.auto_cleanup_enabled !== false) {
|
||||
failures.push('Seed config enables automatic cleanup');
|
||||
}
|
||||
if (config.discord_webhook_url !== '') {
|
||||
failures.push('Seed config contains a webhook');
|
||||
if ('client_secret' in config || 'discord_webhook_url' in config) {
|
||||
failures.push('Seed config contains secret fields');
|
||||
}
|
||||
const Database = require('better-sqlite3');
|
||||
const database = new Database(path.join(environment.appDataDir, 'app.db'));
|
||||
database.exec('CREATE TABLE config_kv (key TEXT PRIMARY KEY, value TEXT NOT NULL, updated_at INTEGER NOT NULL)');
|
||||
database.prepare('INSERT INTO config_kv(key, value, updated_at) VALUES (?, ?, ?)').run('language', JSON.stringify('sqlite'), 1);
|
||||
database.close();
|
||||
if (readE2eConfig(environment).language !== 'sqlite') {
|
||||
failures.push('Helper did not read authoritative SQLite config');
|
||||
}
|
||||
if (!Array.isArray(queue) || queue.length !== 0) {
|
||||
failures.push('Seed queue is not empty');
|
||||
|
||||
@@ -214,7 +214,7 @@ async function run() {
|
||||
assert(deState.deActive, 'German language button did not activate');
|
||||
assert(enState.enActive, 'English language button did not activate');
|
||||
|
||||
await window.api.saveConfig({ client_id: '', client_secret: '' });
|
||||
await window.api.saveConfig({ client_id: '' });
|
||||
window.showTab('vods');
|
||||
await window.selectStreamer('fixture_streamer');
|
||||
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
const { _electron: electron } = require('playwright');
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const {
|
||||
createE2eEnvironment,
|
||||
writeE2eConfig,
|
||||
@@ -86,6 +88,9 @@ async function run() {
|
||||
await app.close();
|
||||
app = null;
|
||||
|
||||
for (const filename of ['app.db', 'app.db-wal', 'app.db-shm']) {
|
||||
fs.rmSync(path.join(environment.appDataDir, filename), { force: true });
|
||||
}
|
||||
writeE2eConfig(environment, {
|
||||
download_mode: 'full',
|
||||
part_minutes: 120
|
||||
|
||||
Reference in New Issue
Block a user