feat(security): encrypt persisted provider credentials
Protect every remembered provider credential with Electron safeStorage and restore it only inside the main process. Migrate legacy plaintext settings atomically across the primary config and its backup while keeping credentials memory-only when encryption is unavailable or remembering is disabled. Initialize credential protection after app readiness but before settings are loaded, add masked renderer projection metadata, and cover encryption, migration, unavailable-storage, and persistence behavior with focused tests.
This commit is contained in:
+16
-11
@@ -1,6 +1,6 @@
|
||||
import fs from "node:fs";
|
||||
import path from "node:path";
|
||||
import { app, BrowserWindow, clipboard, dialog, ipcMain, IpcMainInvokeEvent, Menu, shell, Tray } from "electron";
|
||||
import { app, BrowserWindow, clipboard, dialog, ipcMain, IpcMainInvokeEvent, Menu, safeStorage, shell, Tray } from "electron";
|
||||
import { AddLinksPayload, AppSettings, DebridProvider, EnableRemoteDiagnosticsInput, UpdateInstallProgress } from "../shared/types";
|
||||
import { AppController } from "./app-controller";
|
||||
import { IPC_CHANNELS } from "../shared/ipc";
|
||||
@@ -13,6 +13,7 @@ import { cleanupStaleSubstDrives, shutdownDaemon } from "./extractor";
|
||||
import { revealHistoryEntry } from "./history-reveal";
|
||||
import { DEV_SERVER_URL } from "./dev-server-url";
|
||||
import { resolveAppIconPath } from "./app-icon";
|
||||
import { configureCredentialProtector } from "./credential-protection";
|
||||
|
||||
function validateString(value: unknown, name: string): string {
|
||||
if (typeof value !== "string") {
|
||||
@@ -73,7 +74,7 @@ let clipboardTimer: ReturnType<typeof setInterval> | null = null;
|
||||
let updateQuitTimer: ReturnType<typeof setTimeout> | null = null;
|
||||
let scheduledStartTimer: ReturnType<typeof setTimeout> | null = null;
|
||||
let lastClipboardText = "";
|
||||
const controller = new AppController();
|
||||
let controller: AppController;
|
||||
const CLIPBOARD_MAX_TEXT_CHARS = 50_000;
|
||||
|
||||
function isDevMode(): boolean {
|
||||
@@ -859,8 +860,10 @@ app.on("second-instance", () => {
|
||||
}
|
||||
});
|
||||
|
||||
app.whenReady().then(() => {
|
||||
cleanupStaleSubstDrives();
|
||||
app.whenReady().then(() => {
|
||||
configureCredentialProtector(safeStorage);
|
||||
controller = new AppController();
|
||||
cleanupStaleSubstDrives();
|
||||
registerIpcHandlers();
|
||||
mainWindow = createWindow();
|
||||
bindMainWindowLifecycle(mainWindow);
|
||||
@@ -892,10 +895,12 @@ app.on("before-quit", () => {
|
||||
if (updateQuitTimer) { clearTimeout(updateQuitTimer); updateQuitTimer = null; }
|
||||
stopClipboardWatcher();
|
||||
destroyTray();
|
||||
shutdownDaemon();
|
||||
try {
|
||||
controller.shutdown();
|
||||
} catch (error) {
|
||||
logger.error(`Fehler beim Shutdown: ${String(error)}`);
|
||||
}
|
||||
});
|
||||
shutdownDaemon();
|
||||
if (controller) {
|
||||
try {
|
||||
controller.shutdown();
|
||||
} catch (error) {
|
||||
logger.error(`Fehler beim Shutdown: ${String(error)}`);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user