From b0a2eeaf7205b8f45fd0589d5eb2c04f5d789d88 Mon Sep 17 00:00:00 2001 From: thePR0M3TH3AN <53631862+PR0M3TH3AN@users.noreply.github.com> Date: Wed, 1 Jul 2026 20:01:54 -0400 Subject: [PATCH] [auth] Storage + NWC inherit the kept-unlocked signer on refresh (#51) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Storage and NWC have no passphrase of their own — the storage master key and the NWC settings are encrypted WITH the signer (nip44/nip04 self-encryption) and unlocked by it. So instead of caching a second decrypted secret, both now inherit the kept-unlocked nsec signer. _initAutoLogin restores the cached signer (nostrClient.restoreUnlockedSigner) BEFORE the boot re-login, so: - the login flow's NWC hydrate + list decrypt run with the signer present, and - the existing storage auto-unlock (ProfileStorageController / UploadModal call storageService.unlock when a signer is available) succeeds silently, all on refresh with no passphrase and no manual "Unlock storage". This is safe for nsec (the only way the signer returns on reload, since the key is passphrase- encrypted) and a no-op for NIP-07/46 (nothing cached; their signer already persists). authService.login never touches the signer registry, so restoring before login doesn't get clobbered. Persistence follows the signer cache: storage/NWC stay unlocked exactly as long as the kept-unlocked key does (the session tier, or until site data is cleared if the user checked "keep unlocked"). Logout already forgets the cached key, re-locking all three together. Lint + build green; unlock/switch/session-actor suites green. Co-Authored-By: Claude Opus 4.8 --- js/app.js | 16 ++++++++++++++++ todo/TODO_2026-06-20_pre-launch.md | 22 ++++++++++++++++++---- 2 files changed, 34 insertions(+), 4 deletions(-) diff --git a/js/app.js b/js/app.js index 88c6809d..9799f1b7 100644 --- a/js/app.js +++ b/js/app.js @@ -1217,6 +1217,22 @@ class Application { loginOptions.providerId = savedEntry.providerId; } + // Restore a kept-unlocked nsec signer from cache BEFORE re-login, so the + // login flow (list decrypt, NWC hydrate) and the later storage auto-unlock + // all inherit it on refresh without a passphrase (TODO #51). For nsec this + // is the only way the signer comes back on reload (the key is passphrase- + // encrypted); safe no-op when nothing is cached or for NIP-07/46. + try { + if (typeof nostrClient.restoreUnlockedSigner === "function") { + await nostrClient.restoreUnlockedSigner(normalizedSaved); + } + } catch (error) { + devLogger.warn( + "[Application] Failed to restore kept-unlocked signer on boot:", + error, + ); + } + try { await this.authService.login(savedPubKey, loginOptions); } catch (error) { diff --git a/todo/TODO_2026-06-20_pre-launch.md b/todo/TODO_2026-06-20_pre-launch.md index bef87cdc..cf71d605 100644 --- a/todo/TODO_2026-06-20_pre-launch.md +++ b/todo/TODO_2026-06-20_pre-launch.md @@ -1592,13 +1592,27 @@ passphrase-encrypted). Items 51, 56, 57 are all facets of that. works. Logout / remove-saved-profile forget the cached key (`forgetUnlockedSigner`). Tests: `tests/unlocked-key-cache.test.mjs` (8). Lint + build green. -- [ ] **VERIFY on unstable:** unlock once (leave the box unchecked) → refresh → no PIN; +- [x] **Storage + NWC inherit the kept-unlocked signer — DONE 2026-07-01.** Storage + and NWC don't have their own passphrase: the storage master key and the NWC + settings are ENCRYPTED WITH THE SIGNER (nip44/nip04 self-encryption) and unlocked + by it. So rather than caching a second secret, both now inherit the kept-unlocked + nsec signer. `_initAutoLogin` calls `nostrClient.restoreUnlockedSigner(pubkey)` + BEFORE the boot re-login, so the login flow (NWC hydrate, list decrypt) and the + existing storage auto-unlock (ProfileStorageController / UploadModal call + `storageService.unlock` when a signer is present) all succeed silently on refresh + — no passphrase, no manual "Unlock storage". Tied to the signer cache: persists + exactly as long as the kept-unlocked key does (session or until site-data clear). + (NIP-07/46 profiles no-op — nothing is cached; their extension/remote signer + already persists.) +- [ ] **VERIFY on unstable:** unlock once (box unchecked) → refresh → no PIN, and + Storage shows Unlocked + the wallet/NWC connection is live without re-entry; close the tab + reopen → PIN again. Check the box → close/reopen the browser → - still no PIN; Log out or clear site data → PIN again. Confirm the checkbox warning - copy reads clearly. + still no PIN and storage/NWC still ready; Log out or clear site data → PIN again. - [ ] **Follow-up (optional):** a visible "Lock now / forget on this device" control in the profile menu (today the cache is cleared only on logout / clear-site-data), and - consider surfacing whether the current session is persistently remembered. + consider surfacing whether the current session is persistently remembered. When it + lands, "Lock now" should also `storageService.lock(pubkey)` + drop the NWC cache so + the inherited unlocks re-lock together. ### 52. After editing a video, the video grids don't refresh to show the update (BUG) - [ ] Editing a video leaves the grids showing the pre-edit version until a manual