fix: let a user who lost the password delete the wallet, and warn before they can (closes #312)
Deleting a wallet was password-gated and importing its recovery phrase again was refused as a duplicate xpub by findWalletByXpub(), so a user who held the phrase but had forgotten the password could neither leave the wallet nor come back to it. The only escape was clearing extension storage through browser internals, which takes every other wallet with it, and nothing in the product ever warned that this was possible. DeleteWallet now offers "I have lost my password", a screen that destroys the wallet after the user types its name back. No password: requiring one to discard a secret protects nobody, because an attacker at the popup who wants the wallet gone can uninstall the extension, so the only person such a gate stops is the owner who forgot it. The typed name is a check that the user knows which wallet they are on, so it is matched with letter case, surrounding spaces and repeated inner spaces ignored. The last of those is not a nicety: HTML collapses a doubled inner space when it renders the name, so comparing raw would leave a wallet named "My Wallet" with a confirmation no typing could ever satisfy. This is the deletion route rather than the re-import route, and only one of the two. Re-import would have had to be built three times over (hd and xprv by xpub, key by address), would make the user retype the recovery phrase into a live popup in order to change a password, and reaches no end state that delete-then-import does not already reach through the existing import path and scanForAddresses(). Both routes share one finishDelete(), so the selection repair, the site permission cleanup and the AUTISTMASK_ACTIVE_CHANGED broadcast cannot diverge between them. The new screen is not in RESTORABLE_VIEWS, alongside delete-wallet-confirm: a popup reopened by accident must not land on a button that erases key material. It registers an onViewLeave() cleanup as well, not because a wallet name is a secret but because a typed confirmation left standing in a hidden view leaves a wallet one click from deletion. The two delete screens are siblings, so nothing is pushed on the way in and Back re-enters DeleteWallet through show(), which hands it back its wallet selection. AddWallet's password hint now states, per import mode, that the password cannot be recovered or reset and names what the only backup is. The hint line reserves the 48px all three wordings measure in the popup, so switching tabs cannot move the password fields under the pointer and the reserve costs no height the screen needs elsewhere. The test drives the real view against a chrome.storage.local stub that structured-clones on both set and get, and asserts against what comes back out of storage rather than against the live state object, so it fails on the deletion of saveState() and not only on an in-memory splice.
This commit is contained in:
26
TODO.md
26
TODO.md
@@ -44,6 +44,32 @@ but the review is broader than any of them.
|
||||
|
||||
# Completed Steps
|
||||
|
||||
- 2026-08-20: A forgotten password no longer wedges the wallet
|
||||
([#312](https://git.eeqj.de/sneak/AutistMask/issues/312)). Deleting a wallet
|
||||
was password-gated and importing its recovery phrase again was refused as a
|
||||
duplicate xpub, so a user who had the phrase but not the password could
|
||||
neither leave nor come back: the only way out was clearing extension storage
|
||||
through browser internals, which takes every other wallet with it.
|
||||
DeleteWallet now offers "I have lost my password", a screen that destroys the
|
||||
wallet after the user types its name back — no password, because requiring one
|
||||
to _discard_ a secret protects nobody. An attacker at the popup who wants the
|
||||
wallet gone can uninstall the extension; the only person such a gate stopped
|
||||
was the owner who forgot it. That was chosen over allowing a duplicate xpub to
|
||||
re-encrypt in place: re-import would have had to be built three times over
|
||||
(`hd` and `xprv` by xpub, `key` by address), would make the user retype the
|
||||
recovery phrase into a live popup to change a password, and reaches no state
|
||||
that delete-then-import does not already reach through `scanForAddresses()`.
|
||||
Both routes share one `finishDelete()`, so the selection repair, the
|
||||
site-permission cleanup and the `AUTISTMASK_ACTIVE_CHANGED` broadcast cannot
|
||||
diverge between them, and the new screen is excluded from `RESTORABLE_VIEWS` —
|
||||
a popup reopened by accident must not land on a button that erases key
|
||||
material. AddWallet's password hint now says, per import mode, that the
|
||||
password cannot be recovered or reset and what the only backup is; the hint
|
||||
line reserves its height so switching tabs cannot move the password fields.
|
||||
The test drives the real view against a `chrome.storage.local` stub that
|
||||
structured-clones on both `set` and `get` and asserts against the read-back,
|
||||
so it fails on the deletion of `saveState()` and not only on an in-memory
|
||||
splice.
|
||||
- 2026-08-20: `make build` can no longer hand back a debug build, and
|
||||
`script/verify-build` can no longer be satisfied by bytes the build did not
|
||||
produce ([#309](https://git.eeqj.de/sneak/AutistMask/issues/309)). The
|
||||
|
||||
Reference in New Issue
Block a user