b507cb5986
identity.json previously stored `privKeyHex` and `nsec` as plaintext fields. 0600 file perms keep other OS users out, but not another app running as the same user — and that is the threat model the CLI actually cares about. Introduce a SecretStore indirection: * identity.json now persists only the public parts plus a typed `secret: IdentitySecret` envelope (keychain | ncryptsec | plaintext). * macOS uses `/usr/bin/security` (Keychain ACLs bind the item to the binary that stored it, so other same-user apps need user consent). * Linux uses `secret-tool` if a Secret Service is running on the session D-Bus; the gain there is at-rest encryption while the keyring is locked. * On any platform without a keychain, auto falls back to NIP-49 (scrypt + XChaCha20) with the passphrase read from --passphrase-file, then $AMY_PASSPHRASE, then a TTY prompt. Another same-user app can read the blob but cannot decrypt it without the passphrase. * `--secret-backend=plaintext` is an explicit opt-in for dev scripts and the interop test harness. Legacy identity.json files that still carry top-level privKeyHex/nsec are read transparently and auto-migrate on the next save. whoami, `create` / `login` existence checks, and init-re-run now use a metadata-only load path so they do not trigger a keychain prompt or ask for a passphrase just to echo the npub. Tests: cli/tests/ setups wire --secret-backend=plaintext through the amy_a / amy_d wrappers so headless CI runs do not stall on a TTY passphrase prompt. https://claude.ai/code/session_01SqdMfLdXvb3GskFLcEj739
59 lines
2.0 KiB
Bash
59 lines
2.0 KiB
Bash
# shellcheck shell=bash
|
|
#
|
|
# helpers.sh — thin wrappers that keep the per-test code tight.
|
|
|
|
# --- amy wrapper -------------------------------------------------------------
|
|
# `--secret-backend=plaintext` keeps these throwaway interop runs headless —
|
|
# the default `auto` would try the OS keychain (not available in CI) and then
|
|
# ask for a NIP-49 passphrase. Plaintext still writes 0600-owner-only.
|
|
amy_a() { "$AMY_BIN" --data-dir "$A_DIR" --secret-backend plaintext "$@"; }
|
|
|
|
# Run amy, log stderr, surface JSON on stdout, remember last result.
|
|
amy_json() {
|
|
local out
|
|
if ! out=$(amy_a "$@" 2>>"$LOG_FILE"); then
|
|
fail_msg "amy $*: exit $? (see $LOG_FILE)"
|
|
printf '%s\n' "$out" >>"$LOG_FILE"
|
|
return 1
|
|
fi
|
|
printf '%s' "$out"
|
|
}
|
|
|
|
# Convenience extractors — the CLI emits one JSON object per success so we can
|
|
# jq with impunity.
|
|
amy_field() {
|
|
# usage: amy_field '.group_id' init [args...]
|
|
local path="$1"; shift
|
|
amy_json "$@" | jq -r "$path"
|
|
}
|
|
|
|
# --- assertion helpers -------------------------------------------------------
|
|
# Assert a substring is present in a variable; append a failed result on miss
|
|
# and return 1. Positive case just logs.
|
|
assert_contains() {
|
|
local haystack="$1" needle="$2" test_id="$3" note="${4:-}"
|
|
if [[ "$haystack" == *"$needle"* ]]; then
|
|
info "assertion hit: $test_id contains \"$needle\""
|
|
return 0
|
|
fi
|
|
fail_msg "$test_id: missing \"$needle\" (${note:-no note})"
|
|
info "actual: $haystack"
|
|
record_result "$test_id" fail "${note:-missing \"$needle\"}"
|
|
return 1
|
|
}
|
|
|
|
# Assert two strings are equal (leniently trimmed).
|
|
assert_eq() {
|
|
local actual="$1" expected="$2" test_id="$3" note="${4:-}"
|
|
if [[ "${actual// /}" == "${expected// /}" ]]; then
|
|
info "assertion hit: $test_id \"$actual\" == \"$expected\""
|
|
return 0
|
|
fi
|
|
fail_msg "$test_id: expected \"$expected\", got \"$actual\" (${note:-})"
|
|
record_result "$test_id" fail "${note:-mismatch}"
|
|
return 1
|
|
}
|
|
|
|
# --- wn-side pollers (delegates to lib.sh) -----------------------------------
|
|
# Both exist in lib.sh already; this file only adds headless-specific niceties.
|