Skip to content

feat(desktop): gate sign-out behind key backup + typed confirmation#2424

Merged
wesbillman merged 1 commit into
mainfrom
carl/signout-safety
Jul 22, 2026
Merged

feat(desktop): gate sign-out behind key backup + typed confirmation#2424
wesbillman merged 1 commit into
mainfrom
carl/signout-safety

Conversation

@wesbillman

@wesbillman wesbillman commented Jul 22, 2026

Copy link
Copy Markdown
Collaborator

Problem

Signing out wipes the identity key and all local data, but the confirm dialog was a single unguarded click — one red button between the user and permanent, irrecoverable key destruction.

Raised by Wes in improve-signout-safety.

Change

The dialog now requires two explicit steps before "Delete My Data" unlocks:

  1. Back up the key — the nsec is shown inline (masked, reveal/copy via the existing NsecMaskedDisplay); the "I have saved my private key" checkbox only unlocks after the user actually reveals or copies the key. If the key can't be loaded (keychain error) the checkbox stays usable so sign-out is never soft-bricked.
  2. Typed confirmation — the user must type wipe all my data (trimmed, case-insensitive).

Cancel remains the focused default (Radix AlertDialog), so Enter can never fire the wipe.

Implementation notes

  • Sign-out section extracted from ProfileSettingsCard.tsx into a new SignOutSection.tsx, bringing ProfileSettingsCard back under the 1000-line ceiling; its TEMP file-size override is removed.
  • The confirm button is a plain Button rather than AlertDialogAction: Radix's Action closes the dialog on click, which would drop the pending spinner while the wipe + restart is in flight.
  • NsecMaskedDisplay gains an optional onKeyInteraction callback (fired on reveal/copy) so flows can gate on real interaction with the key.
  • e2e mock bridge now handles sign_out (no-op; specs assert invocation via __BUZZ_E2E_COMMANDS__).

Tests

New signout-confirmation.spec.ts (registered in the smoke project):

  • delete button unlocks only after backup + exact phrase (wrong phrase stays locked)
  • reveal also unlocks the backup checkbox
  • completing both gates invokes sign_out
  • cancel resets all gates for the next open
  • keychain-error degradation path still allows sign-out

Verified at d6023d1: pnpm typecheck, pnpm check, full pnpm test (3401 pass), and smoke e2e for the touched specs (14 pass).

Screenshots

New dialog — "Delete My Data" locked until both gates pass:

signout-dialog

Settings section (unchanged entry point):

signout-section

Signing out wipes the identity key and all local data, but the confirm
dialog was a single unguarded click — one red button between the user and
permanent, irrecoverable key destruction.

The dialog now requires two explicit steps before "Delete My Data"
unlocks:

1. Back up the key — the nsec is shown inline (masked, reveal/copy via
   the existing NsecMaskedDisplay); the "I have saved my private key"
   checkbox only unlocks after the user actually reveals or copies the
   key. If the key cannot be loaded (keychain error) the checkbox stays
   usable so sign-out is never soft-bricked.
2. Typed confirmation — the user must type "wipe all my data"
   (trimmed, case-insensitive).

Implementation notes:
- The sign-out section moves out of ProfileSettingsCard into a new
  SignOutSection.tsx, bringing ProfileSettingsCard back under the
  1000-line ceiling; its TEMP file-size override is removed.
- The confirm button is a plain Button rather than AlertDialogAction:
  Radix's Action closes the dialog on click, which would drop the
  pending spinner while the wipe + restart is still in flight.
- NsecMaskedDisplay gains an optional onKeyInteraction callback (fired
  on reveal/copy) so flows can gate on real interaction with the key.
- The e2e mock bridge now handles sign_out (no-op; specs assert
  invocation via __BUZZ_E2E_COMMANDS__).
- New signout-confirmation.spec.ts covers gate unlock ordering, wrong
  phrase, sign_out invocation, cancel-resets-state, and the
  keychain-error degradation path.

Co-authored-by: Wes <wesb@block.xyz>
Signed-off-by: Wes <wesb@block.xyz>
@wesbillman
wesbillman requested a review from a team as a code owner July 22, 2026 22:05
@wesbillman

Copy link
Copy Markdown
Collaborator Author

Princess Donut review at d6023d17686be567210d20cdd7c1b5f0e758a7c6: no blocking findings.

The two gates are independently enforced, only successful copy/reveal unlocks backup attestation, dialog state resets on cancel/failure, late get_nsec results are ignored after close, the destructive action cannot be submitted with Enter, and the keychain-error escape hatch avoids trapping the user. The extraction also preserves the prior success/failure storage-clearing behavior.

Verification at that SHA: git diff --check, typecheck, Biome/file-size guards, and the full desktop unit suite (3401/3401) passed locally. All required GitHub checks, including all four Desktop Smoke E2E shards, are green.

I attempted to record an approval, but the authenticated GitHub account is the PR owner, so GitHub correctly rejected self-approval.

@wesbillman
wesbillman merged commit fd967c6 into main Jul 22, 2026
25 checks passed
@wesbillman
wesbillman deleted the carl/signout-safety branch July 22, 2026 22:38
loganj pushed a commit that referenced this pull request Jul 22, 2026
…ad-lifecycle

* origin/main:
  fix(desktop): strip GIF metadata extensions before upload (#2425)
  feat(desktop): gate sign-out behind key backup + typed confirmation (#2424)
  ci(desktop): add signed macOS canary build (#2419)
  feat(desktop+acp): spawn a harness per (agent, community) pair at GUI startup — warm sockets, lazy LLM pool (#2122)
  Show team count separately in channel template rows (#2404)
  fix(dev): restore shared worktree identity from keyring (#2400)
  fix(onboarding): skip community profile setup for existing relay members (#2300)
  fix(mobile): preserve and display sidebar section icons (#2403)
  Unify sidebar chrome across themes (#2380)
  feat(media): add S3-truth per-community storage sweep (#2044)
brow pushed a commit that referenced this pull request Jul 22, 2026
…obile-releasing

Co-authored-by: npub1sv749mw8zcmld4ygjx2mx2aqcn3zvtuj2nlmgatxgad3t9uweu9q5marze <833d52edc71637f6d4889195b32ba0c4e2262f9254ffb47566475b15978ecf0a@sprout-oss.stage.blox.sqprod.co>

Signed-off-by: npub1sv749mw8zcmld4ygjx2mx2aqcn3zvtuj2nlmgatxgad3t9uweu9q5marze <833d52edc71637f6d4889195b32ba0c4e2262f9254ffb47566475b15978ecf0a@sprout-oss.stage.blox.sqprod.co>

* origin/main:
  fix(desktop): strip GIF metadata extensions before upload (#2425)
  feat(desktop): gate sign-out behind key backup + typed confirmation (#2424)
  ci(desktop): add signed macOS canary build (#2419)
  feat(desktop+acp): spawn a harness per (agent, community) pair at GUI startup — warm sockets, lazy LLM pool (#2122)
  Show team count separately in channel template rows (#2404)
  fix(dev): restore shared worktree identity from keyring (#2400)
  fix(onboarding): skip community profile setup for existing relay members (#2300)
  fix(mobile): preserve and display sidebar section icons (#2403)
  Unify sidebar chrome across themes (#2380)
  feat(media): add S3-truth per-community storage sweep (#2044)
  feat(relay): log NIP-98 pubkey attribution on HTTP bridge requests (#2206)

Co-authored-by: npub1sv749mw8zcmld4ygjx2mx2aqcn3zvtuj2nlmgatxgad3t9uweu9q5marze <833d52edc71637f6d4889195b32ba0c4e2262f9254ffb47566475b15978ecf0a@sprout-oss.stage.blox.sqprod.co>
Signed-off-by: npub1sv749mw8zcmld4ygjx2mx2aqcn3zvtuj2nlmgatxgad3t9uweu9q5marze <833d52edc71637f6d4889195b32ba0c4e2262f9254ffb47566475b15978ecf0a@sprout-oss.stage.blox.sqprod.co>

# Conflicts:
#	RELEASING.md
loganj pushed a commit that referenced this pull request Jul 22, 2026
…ad-lifecycle

* origin/main:
  fix(desktop): strip GIF metadata extensions before upload (#2425)
  feat(desktop): gate sign-out behind key backup + typed confirmation (#2424)
  ci(desktop): add signed macOS canary build (#2419)
  feat(desktop+acp): spawn a harness per (agent, community) pair at GUI startup — warm sockets, lazy LLM pool (#2122)
  Show team count separately in channel template rows (#2404)
  fix(dev): restore shared worktree identity from keyring (#2400)
  fix(onboarding): skip community profile setup for existing relay members (#2300)
  fix(mobile): preserve and display sidebar section icons (#2403)
  Unify sidebar chrome across themes (#2380)
  feat(media): add S3-truth per-community storage sweep (#2044)

Signed-off-by: npub1dpf98sl35hm9k65t8h6cvh5n6knn2msugh5k5nmysxwnw5wlh7uqn2wgp4 <685253c3f1a5f65b6a8b3df5865e93d5a7356e1c45e96a4f64819d3751dfbfb8@sprout-oss.stage.blox.sqprod.co>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants