Add password-encrypted vault

- Encrypt stored secret keys with AES-256-GCM under an Argon2id-derived key;
  the vault stays plaintext until a password is set (Settings -> Storage or
  the CLI set-password command)
- Only secret keys are encrypted; labels and npubs stay readable so profiles
  can be browsed while the vault is locked
- Backend: crypto module, Vault.crypto metadata, unlock/lock/set/remove
  password on App, VaultLocked/WrongPassword errors, secret resolution on the
  publish path
- IPC: set_vault_password, unlock_vault, lock_vault, remove_vault_password
- CLI: set-password, remove-password, unlock; create/publish prompt when the
  vault is locked (NFM_PASSWORD env or hidden prompt, never argv)
- GUI: unlock banner + modal on locked vaults, protect/change/remove password
  in Settings, password field styling
- Tests: Rust (argon2/AES round-trips, vault lifecycle) and Vitest (unlock
  flow, set/change/remove password), all green
This commit is contained in:
Avi 2026-08-03 19:00:41 -05:00
commit 7ca1d14dcb
23 changed files with 1540 additions and 43 deletions

View file

@ -540,6 +540,7 @@ a {
input[type='text'],
input[type='search'],
input[type='password'],
select,
textarea {
font: inherit;
@ -552,6 +553,8 @@ textarea {
}
input[type='text']:focus-visible,
input[type='search']:focus-visible,
input[type='password']:focus-visible,
select:focus-visible,
textarea:focus-visible {
outline: none;
@ -1243,6 +1246,23 @@ select {
padding: 12px 14px;
}
.lock-banner {
padding: 14px 22px 0;
}
.lock-banner .alert {
max-width: 1080px;
margin: 0 auto;
}
.lock-banner-row {
display: flex;
align-items: center;
justify-content: space-between;
gap: 14px;
flex-wrap: wrap;
}
.path-row > div {
min-width: 0;
}