mirror of
https://github.com/PR0M3TH3AN/SeedPass.git
synced 2026-11-02 19:02:06 +00:00
- Python-compatible ~/.seedpass layout: fingerprints.json (same shape, indent=4), per-profile dirs with parent_seed.enc (kdf/ct wrapper; unlock honors argon2id metadata and falls back across pbkdf2 iteration counts), index-key-encrypted seedpass_config.json.enc with ConfigManager defaults. - CLI: fingerprint list/add/switch/remove (remove requires --yes), config get/set (JSON-parsed values, pin/password hashes redacted on read), profile-aware vault resolution (--vault override, else active profile). - Session agent (the chosen unlock model): unix-socket daemon holding unlocked seeds in memory with TTL, 0600 socket; agent start/status/stop, vault unlock (password -> parent seed -> agent, never argv) and vault lock. Seed resolution order: SEEDPASS_MNEMONIC env, then agent. This daemon is the designed enforcement point for the section-9.3 lease/token layer. - Tests cover the full no-env-mnemonic lifecycle: locked error -> unlock with password only -> provision/reveal through the agent -> status -> lock -> locked again. 148 core + 29 CLI tests green. Co-Authored-By: Claude Fable 5 <[email protected]>
65 lines
1.9 KiB
TypeScript
65 lines
1.9 KiB
TypeScript
/**
|
|
* Per-profile encrypted configuration, parity with ConfigManager
|
|
* (seedpass_config.json.enc, encrypted under the index key).
|
|
*/
|
|
|
|
import { readFile, writeFile } from "node:fs/promises";
|
|
import { existsSync } from "node:fs";
|
|
import { join } from "node:path";
|
|
import { decryptPayload, deriveIndexKeyBytes, encryptV3, utf8 } from "@seedpass/core";
|
|
import { CONFIG_FILENAME } from "./appDir.js";
|
|
|
|
export const DEFAULT_RELAYS = [
|
|
"wss://relay.snort.social",
|
|
"wss://nostr.oxtr.dev",
|
|
"wss://relay.primal.net",
|
|
];
|
|
|
|
/** Defaults mirror ConfigManager.load_config. */
|
|
export function defaultConfig(): Record<string, unknown> {
|
|
return {
|
|
relays: [...DEFAULT_RELAYS],
|
|
offline_mode: false,
|
|
online_mode_notice_seen: false,
|
|
pin_hash: "",
|
|
password_hash: "",
|
|
inactivity_timeout: 900,
|
|
kdf_iterations: 200000,
|
|
kdf_mode: "pbkdf2",
|
|
argon2_time_cost: 2,
|
|
additional_backup_path: "",
|
|
backup_interval: 0,
|
|
secret_mode_enabled: false,
|
|
clipboard_clear_delay: 45,
|
|
quick_unlock_enabled: false,
|
|
nostr_max_retries: 2,
|
|
nostr_retry_delay: 1.0,
|
|
min_uppercase: 2,
|
|
min_lowercase: 2,
|
|
min_digits: 2,
|
|
min_special: 2,
|
|
};
|
|
}
|
|
|
|
export async function loadConfig(
|
|
profileDir: string,
|
|
mnemonic: string,
|
|
): Promise<Record<string, unknown>> {
|
|
const path = join(profileDir, CONFIG_FILENAME);
|
|
if (!existsSync(path)) return defaultConfig();
|
|
const key = deriveIndexKeyBytes(mnemonic);
|
|
const blob = new Uint8Array(await readFile(path));
|
|
const plain = await decryptPayload(key, blob);
|
|
return { ...defaultConfig(), ...(JSON.parse(new TextDecoder().decode(plain)) as object) };
|
|
}
|
|
|
|
export async function saveConfig(
|
|
profileDir: string,
|
|
mnemonic: string,
|
|
config: Record<string, unknown>,
|
|
): Promise<void> {
|
|
const key = deriveIndexKeyBytes(mnemonic);
|
|
const payload = await encryptV3(key, utf8(JSON.stringify(config)));
|
|
await writeFile(join(profileDir, CONFIG_FILENAME), payload, { mode: 0o600 });
|
|
}
|