theseus/settings-preload.js
Local Dev de7735feb3 Theseus: quick-unlock PIN for the vault, shared with extensions
The vault re-locks on every restart and only the master password opened
it, so every extension that needs it (Aegis, now Pithos) either asked for
the master password itself or grew its own PIN. Theseus now owns one:

- Settings > Passwords sets, changes or removes a 6-digit PIN. The PIN
  wraps the master password (PBKDF2-SHA256, 600k iterations, AES-256-GCM)
  and the result is sealed with the OS keystore (safeStorage: DPAPI /
  Keychain / libsecret), so a copied vault-pin.json cannot be brute-forced
  elsewhere. Every unlock still ends at the master password.
- Three wrong PINs in a row require the master password. The strike count
  lives in the same file, so a restart does not reset it; a successful
  master-password unlock does. A PIN whose password no longer opens the
  vault (password changed) is dropped.
- unlock.html is Theseus's own prompt, over the whole window: PIN pad, or
  the master password. Extensions call api.vault.requestUnlock({ reason })
  (vault-derive capability) and get { ok } back; what the user typed never
  reaches them. Settings' locked screen offers "Unlock with PIN" through
  the same prompt.
2026-10-03 20:33:26 +02:00

93 lines
6.5 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

const { contextBridge, ipcRenderer } = require("electron");
contextBridge.exposeInMainWorld("cfg", {
get: () => ipcRenderer.invoke("settings-get"),
set: (key, value) => ipcRenderer.invoke("settings-set", key, value),
engines: () => ipcRenderer.invoke("search-engines"),
addEngine: (eng) => ipcRenderer.invoke("add-engine", eng),
removeEngine: (id) => ipcRenderer.invoke("remove-engine", id),
setEngineEnabled: (id, on) => ipcRenderer.invoke("set-engine-enabled", id, on),
setEngineOrder: (ids) => ipcRenderer.invoke("set-engine-order", ids),
removeFromList: (id) => ipcRenderer.invoke("remove-from-list", id),
// Storage: wipe browsing data on demand. Pass any subset of
// { cookies, cache, storage, history }.
clearBrowsingData: (opts) => ipcRenderer.invoke("clear-browsing-data", opts),
// Password vault. All calls return { ok, ... } | { ok: false, err }.
// Renderers never see the seed / vault key / master password past setup/
// unlock; get() returns plaintext only in explicit response to a user click.
pwStatus: () => ipcRenderer.invoke("password-status"),
pwSetup: (masterPassword, seedSource) => ipcRenderer.invoke("password-setup", { masterPassword, seedSource }),
pwUnlock: (masterPassword) => ipcRenderer.invoke("password-unlock", masterPassword),
pwLock: () => ipcRenderer.invoke("password-lock"),
pwList: () => ipcRenderer.invoke("password-list"),
pwGet: (id) => ipcRenderer.invoke("password-get", id),
pwAdd: (entry) => ipcRenderer.invoke("password-add", entry),
pwUpdate: (id, patch) => ipcRenderer.invoke("password-update", id, patch),
pwRemove: (id) => ipcRenderer.invoke("password-remove", id),
pwGenerate: (spec) => ipcRenderer.invoke("password-generate", spec),
// Quick-unlock PIN. pinSet proves the master password in main before
// wrapping it; pinUnlock opens Theseus's own PIN / password prompt.
pinStatus: () => ipcRenderer.invoke("vault-pin-status"),
pinSet: (pin, masterPassword) => ipcRenderer.invoke("vault-pin-set", { pin, masterPassword }),
pinClear: () => ipcRenderer.invoke("vault-pin-clear"),
pinUnlock: () => ipcRenderer.invoke("vault-pin-unlock"),
// Main asks settings to jump to a specific sidebar section (e.g. from the
// engine picker's "Search settings…" click). Emits the section id string.
onFocusSection: (cb) => ipcRenderer.on("focus-section", (_e, section) => cb(section)),
// Collision-mode: BCNR/ICANN policy + per-name/per-TLD overrides
collisionState: () => ipcRenderer.invoke("collision-state"),
setCollisionPolicy: (p) => ipcRenderer.invoke("collision-set-policy", p),
resetCollisions: () => ipcRenderer.invoke("collision-reset"),
// Add-ons management (Settings > Add-ons tab).
listAddons: () => ipcRenderer.invoke("addons-list"),
setAddonEnabled: (id, enabled) => ipcRenderer.invoke("addons-set-enabled", id, !!enabled),
revealAddon: (folder) => ipcRenderer.invoke("addons-reveal", folder),
// Delete a non-bundled extension's folder (Settings › Extensions › ⋯ › Remove).
removeAddon: (id) => ipcRenderer.invoke("addons-remove", id),
openAddonsDir: () => ipcRenderer.invoke("addons-open-dir"),
reloadAddons: () => ipcRenderer.invoke("addons-reload"),
// Add-on update flow. checkAddonUpdates hits the release manifest and
// stages any newer signed version; listStagedAddonUpdates reports what's
// waiting; applyStagedAddons promotes staged → active and reactivates the
// addon host so the new bytes load without a full Theseus restart.
// Community extensions from theseus.x/extensions: the catalog (with what
// is installed already) and a verified install/update of one entry.
communityCatalog: () => ipcRenderer.invoke("addons-community-catalog"),
installCommunity: (id) => ipcRenderer.invoke("addons-install-community", id),
checkAddonUpdates: () => ipcRenderer.invoke("addons-check-updates"),
listStagedAddonUpdates: () => ipcRenderer.invoke("addons-list-staged"),
applyStagedAddons: (id) => ipcRenderer.invoke("addons-apply-staged", typeof id === "string" ? id : undefined),
// Settings › Performance › Protections: talk to an add-on's own message
// handlers (Shield, Cookie Pop-ups) and open its panel for the details.
addonInvoke: (id, msg, payload) => ipcRenderer.invoke("addon-invoke", String(id || ""), String(msg || ""), payload),
openPanel: (panelId) => ipcRenderer.invoke("settings-open-panel", String(panelId || "")),
// Which page is showing (the address bar follows), Tor state + toggle for Privacy › Network.
reportSection: (slug) => ipcRenderer.invoke("settings-section", String(slug || "")),
torState: () => ipcRenderer.invoke("tor-state"),
toggleTor: () => ipcRenderer.invoke("toggle-tor"),
// OS locale — used by the Website-language row to label "Automatic (OS: …)".
systemLocale: () => ipcRenderer.invoke("system-locale"),
// Live settings updates — the toolbar chip, this page's Website-language
// row, and the Anti-fingerprinting Language row all edit the same setting;
// any of them writing pushes a "settings-update" the others react to.
onSettingsUpdate: (cb) => ipcRenderer.on("settings-update", (_e, d) => cb(d)),
// Ariadne's Thread plug-in (system-wide resolver). The state getter returns
// { state:"running"|"stopped"|"not-installed", installedVersion, bundledVersion,
// canUpdate, hasUninstaller }; the mutators prompt UAC for admin.
ariadneState: () => ipcRenderer.invoke("ariadne-state"),
ariadneToggle: (on) => ipcRenderer.invoke("ariadne-toggle", !!on),
ariadneInstall: () => ipcRenderer.invoke("ariadne-install"),
ariadneUpdate: () => ipcRenderer.invoke("ariadne-update"),
ariadneUninstall: () => ipcRenderer.invoke("ariadne-uninstall"),
// Local BNS daemon settings + status (0.1.13+). All read/write against
// C:\ProgramData\Ariadne\policy.json (user-writable ACL) and the daemon's
// own /api/status endpoint on 127.0.0.1. No UAC required for any of these.
ariadneGetStatus: () => ipcRenderer.invoke("ariadne-get-status"),
ariadneGetPolicy: () => ipcRenderer.invoke("ariadne-get-policy"),
ariadneSetPolicy: (policy) => ipcRenderer.invoke("ariadne-set-policy", String(policy || "")),
ariadneSetSource: (name, enabled) => ipcRenderer.invoke("ariadne-set-source", String(name || ""), !!enabled),
// Manual "Check for updates" — un-dismisses any existing chip and re-
// fetches the release manifest. Returns { updateAvailable, currentVersion }.
recheckUpdate: () => ipcRenderer.invoke("recheck-update"),
appVersion: () => ipcRenderer.invoke("app-version"),
restartApp: () => ipcRenderer.invoke("app-restart"),
});