Add reveal-secret-key after unlock (CLI + GUI)

- profiles::reveal_secret_key returns a key in hex and nsec1... forms, gated on
  an unlocked vault (VaultLocked when encrypted + locked)
- IPC: reveal_secret_key method; error replies now carry a machine-readable
  code field (ErrorKind as snake_case, e.g. vault_locked)
- CLI: show-secret <npub> prompts for the vault password when locked
- GUI: 'Secret key' button per profile card opens a modal showing hex + nsec
  with copy buttons; locked vaults ask for the password inline before revealing
- Tests: Rust (reveal plaintext/encrypted/locked) and Vitest (reveal flow,
  lock-then-unlock), all green
This commit is contained in:
Avi 2026-08-04 12:09:30 -05:00
commit 8eb6685281
17 changed files with 594 additions and 54 deletions

View file

@ -1,4 +1,4 @@
# Checkpoint — Password-encrypted vault (2026-08-03)
# Checkpoint — Secret-key reveal after unlock (2026-08-04)
A stopping point you can return to if this session is closed. Everything below was
verified green at the moment this file was written.
@ -6,73 +6,87 @@ verified green at the moment this file was written.
## Where things are
- Project: `/home/avi/Projects/skills/nost-feed-manager`
- Git repo: `master` @ `7e3bac3` ("Add Nostr Feed Manager: Rust backend with Electron + React GUI")
- The encryption work is **uncommitted** — all changes are in the working tree.
- Also relevant: `/home/avi/Projects/nostr_backend/nostr_backendmanager.md` (old-CLI docs, untouched),
and `/home/avi/Projects/nostr_backend/vlog-website/` (separate, untouched).
- Git repo: `master`
- The reveal feature is **uncommitted** — all changes are in the working tree.
- `/home/avi/Projects/nostr_backend/nostr_backendmanager.md` (old-CLI docs) has been updated
to match reality; it lives outside this repo so it is not part of the commit.
- `/home/avi/Projects/nostr_backend/vlog-website/` (separate, untouched).
## What was completed: password-encrypted vault
## What was completed in this session: reveal a secret key after unlock
- Secret keys are now encrypted at rest with **AES-256-GCM** under a key derived via **Argon2id**
from the user's password. Vaults stay plaintext until a password is set (opt-in).
- Encryption only covers the secret keys; labels/npubs stay readable so profiles can be browsed
while the vault is locked. The derived key lives only in memory for the session.
Building on the existing password-encrypted vault (AES-256-GCM + Argon2id), owners can now view a
profile's secret key after entering the vault password:
## Files changed (19 modified, 4 new)
- Backend: `profiles::reveal_secret_key` returns the key in both hex and `nsec1...` forms, gated on
an unlocked vault (`VaultLocked` when encrypted + locked). New CLI command `show-secret <npub>`,
which prompts for the password when locked (via `NFM_PASSWORD` env or hidden prompt).
- IPC: new `reveal_secret_key { npub }` method. Error replies now carry a machine-readable `code`
field (ErrorKind serialised as snake_case, e.g. `vault_locked`), so the GUI can branch without
string-matching on user-facing messages.
- GUI: a "Secret key" button on every profile card opens `ShowSecretKeyModal`, which shows hex +
nsec with copy buttons and a warning. When the vault is locked the modal asks for the password
inline, unlocks, then reveals.
- Key is only ever fetched after unlock; never stored in state before reveal.
## Files changed (16 modified, 2 new)
Modified:
- `Cargo.toml`, `Cargo.lock` — added `argon2`, `aes-gcm`, `base64`, `getrandom`, `rpassword`
- `README.md` — documented the new feature + CLI commands
- `src/lib.rs`, `src/app.rs`, `src/vault.rs`, `src/profiles.rs`, `src/publish.rs`,
`src/ipc.rs`, `src/main.rs`, `src/errors.rs`
- `frontend/src/App.tsx`, `frontend/src/lib/api.ts`, `frontend/src/lib/types.ts`,
`frontend/src/state/AppProvider.tsx`, `frontend/src/screens/SettingsScreen.tsx`,
`frontend/src/styles.css`, `frontend/src/test/apiMock.ts`, `frontend/src/test/fakeBackend.ts`
- `README.md` — documented the reveal feature + `show-secret`
- `src/errors.rs``ErrorKind` now serialises as snake_case for the IPC error code
- `src/profiles.rs``RevealedKey`, `reveal_secret_key`, `profile_label` + tests
- `src/ipc.rs``RevealSecretKey` request, `code` on error replies
- `src/main.rs``show-secret` CLI command
- `frontend/src/components/Icon.tsx` — new `key` icon
- `frontend/src/lib/api.ts``revealSecretKey`, `BackendError.code`
- `frontend/src/lib/types.ts``RevealedKey`, error `code` in `BackendResponse`
- `frontend/src/screens/ProfilesScreen.tsx` — "Secret key" button per profile
- `frontend/src/state/AppProvider.tsx``revealSecretKey` in context
- `frontend/src/test/{App,ProfilesScreen,apiMock,fakeBackend}` — updated for new UI + error codes
New:
- `src/crypto.rs` — Argon2id KDF + AES-256-GCM encrypt/decrypt + password verifier
- `frontend/src/components/UnlockModal.tsx`
- `frontend/src/components/VaultPasswordModal.tsx`
- `frontend/src/test/VaultPassword.test.tsx`
- `frontend/src/components/ShowSecretKeyModal.tsx`
- `frontend/src/test/ShowSecretKey.test.tsx`
Also updated (outside repo): `/home/avi/Projects/nostr_backend/nostr_backendmanager.md`.
## New backend API (IPC + CLI)
IPC methods: `set_vault_password { current_password?, new_password }`,
`unlock_vault { password }`, `lock_vault`, `remove_vault_password { password }`.
IPC: `reveal_secret_key { npub }``{ hex, nsec }`. Error replies now include
`"code": "vault_locked"` (etc.) alongside `message`/`details`.
CLI: `set-password`, `remove-password`, `unlock`; `create`/`publish` auto-prompt when locked.
Passwords come from `NFM_PASSWORD` env var or a hidden terminal prompt — never argv.
Min password length: 8 chars.
CLI: `show-secret <npub>` prints hex + nsec after unlocking. `create`, `publish`, and
`show-secret` all auto-prompt for the vault password when it is encrypted.
## How it was verified (all green)
```
cargo test # 50 passed
cargo test # 55 passed
cargo clippy --all-targets # clean
cargo fmt --check # clean
cargo build --release # builds
npm run typecheck # clean (frontend/)
npm run lint # clean (pre-existing module warning only)
npm run format:check # clean
npm test # 52 passed (10 files)
npm test # 56 passed (11 files)
```
Plus a manual end-to-end CLI smoke test: create → set-password → vault file shows only base64
ciphertext → wrong password rejected → correct password creates encrypted profile. Temp data
was cleaned up (`/tmp/nfm-e2e` removed).
Plus a manual IPC end-to-end smoke test: reveal on plaintext → OK; set-password → lock →
reveal returns `code: "vault_locked"`; unlock → reveal returns the same hex. Temp data cleaned up.
## How to resume
1. Open the repo: `cd /home/avi/Projects/skills/nost-feed-manager`
2. Inspect the diff: `git diff` (work is still uncommitted)
3. To try it: `cargo build --release` then
`XDG_DATA_HOME=/tmp/nfm-smoke ./target/release/nostr-manager-backend create "Alice"`,
`NFM_PASSWORD=... ./target/release/nostr-manager-backend set-password`
3. To try it:
- CLI: `cargo build --release`, then
`XDG_DATA_HOME=/tmp/nfm-smoke ./target/release/nostr-manager-backend create "Alice"` and
`./target/release/nostr-manager-backend show-secret <npub>`
- GUI: `cd frontend && npm start`, Profiles → "Secret key" on a card
4. Re-run verification with the commands above.
## Outstanding / next steps (if you continue)
- Decide whether to **commit** the work (nothing is committed yet).
- `nostr_backendmanager.md` still lists "password-based vault encryption" as a future item and was
left untouched — it may deserve updating to match reality.
- No lock-screen gate: browsing works while locked; only create/publish require unlocking (intended).
- Decide whether to **commit** the reveal work (nothing is committed yet).
- Consider a small UI hint that profiles with an unencrypted vault can be revealed with no prompt.
- Relay defaults are currently `relay.damus.io` (503 upstream) and `relay.nostr.band` (timeout);
user's local settings already point at `nos.lol` + `relay.primal.net` instead.