feat: first-login security setup - recovery key revealed on first sign-in #8

Closed
claude wants to merge 0 commits from feat/e2e-key-onboarding into main
Contributor

From real-user testing feedback: "on first login the app doesn't show the E2E recovery key at all." The engine (createFreshCrossSigningIdentity()) and the reveal+copy UI (RecoveryKeyModal) both existed, but nothing ever surfaced them during sign-in — the key was only reachable through a small icon buried in the space rail's settings.

What this adds

runPostAuthKeySetup() runs after client boot and opens the new FirstLoginSecurityModal in one of two carefully distinguished modes:

  • create — the account has nothing set up anywhere yet (fresh registration / first sign-in): automatically bootstraps cross-signing with the password still in memory from the form (no re-prompt), reveals the recovery key with one-click copy and a plain-language explainer ("the only way to read your old messages on a new device; nobody can recover it for you, not even via password reset"), explicit confirm-to-continue with a visible escape hatch.
  • restore — an identity exists but this device lacks the keys (returning user, new device): never creates a fresh identity (that would reset trust for every other device); offers entering the existing recovery key (restores key backup + cross-signing), verifying from an already-signed-in device (reuses verificationStore), or deferring.
  • Already-set-up devices: no interruption at all.

Test impact

loginAs() (e2e helper) auto-dismisses the modal for the rest of the suite (handleSecuritySetup: false opts out), so no existing spec needed changes. New e2e/tests/firstLoginSecurity.spec.ts covers the create path end-to-end: key auto-created and revealed, copied, dismissed — and does not reappear on reload.

Gate

build clean, 471 unit tests (+1 skip), oxlint 0 errors, full Playwright e2e 14/14 green (13 existing + 1 new). Based on main @ 65fdb32.

Co-Authored-By: Claude Sonnet 5 noreply@anthropic.com

🤖 Generated with Claude Code

From real-user testing feedback: "on first login the app doesn't show the E2E recovery key at all." The engine (`createFreshCrossSigningIdentity()`) and the reveal+copy UI (`RecoveryKeyModal`) both existed, but nothing ever surfaced them during sign-in — the key was only reachable through a small icon buried in the space rail's settings. ## What this adds `runPostAuthKeySetup()` runs after client boot and opens the new `FirstLoginSecurityModal` in one of two carefully distinguished modes: - **create** — the account has nothing set up anywhere yet (fresh registration / first sign-in): automatically bootstraps cross-signing with the password still in memory from the form (no re-prompt), reveals the recovery key with one-click copy and a plain-language explainer ("the only way to read your old messages on a new device; nobody can recover it for you, not even via password reset"), explicit confirm-to-continue with a visible escape hatch. - **restore** — an identity exists but this device lacks the keys (returning user, new device): **never** creates a fresh identity (that would reset trust for every other device); offers entering the existing recovery key (restores key backup + cross-signing), verifying from an already-signed-in device (reuses `verificationStore`), or deferring. - Already-set-up devices: no interruption at all. ## Test impact `loginAs()` (e2e helper) auto-dismisses the modal for the rest of the suite (`handleSecuritySetup: false` opts out), so no existing spec needed changes. New `e2e/tests/firstLoginSecurity.spec.ts` covers the create path end-to-end: key auto-created and revealed, copied, dismissed — and does **not** reappear on reload. ## Gate build clean, 471 unit tests (+1 skip), oxlint 0 errors, full Playwright e2e **14/14 green** (13 existing + 1 new). Based on main @ 65fdb32. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> 🤖 Generated with [Claude Code](https://claude.com/claude-code)
feat: first-login security setup - recovery key revealed on first sign-in
Some checks failed
CI / build-and-test (pull_request) Has been cancelled
CI / e2e (pull_request) Has been cancelled
35023f5546
Real-user feedback: "on first login the app doesn't show the E2E key at
all." The engine (createFreshCrossSigningIdentity) and the reveal+copy UI
(RecoveryKeyModal) both existed, but nothing ever surfaced them during
sign-in - the key was only reachable through a small icon buried in the
space rail's settings.

runPostAuthKeySetup() now runs after client boot and opens the new
FirstLoginSecurityModal in one of two modes, carefully distinguished:

- create: the account has NOTHING set up anywhere yet (fresh
  registration or first sign-in) - automatically bootstraps cross-signing
  with the password still in memory from the login/register form (no
  re-prompt), reveals the recovery key with a one-click copy and a
  plain-language explainer of what it is and what losing it means, and an
  explicit confirm-to-continue with a visible escape hatch.

- restore: an identity already exists but this device lacks the keys
  (returning user, new device) - NEVER creates a fresh identity here
  (that would reset trust for every other device); offers entering the
  existing recovery key (restores key backup + cross-signing) or
  verifying from an already-signed-in device (reuses verificationStore),
  or deferring.

Already-set-up devices get no interruption at all.

loginAs() (e2e helper) auto-dismisses the modal for the rest of the
suite; new firstLoginSecurity.spec.ts covers the create path end-to-end,
including no re-prompt on reload.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BjDnbbumj61ftRMhpLXCKQ
Author
Contributor

Merged into main at 67f6054 (merge commit onto PR #7, which landed first). One real conflict: PLAN.md dev-log entries from PR #7 and PR #8 both appended after the same prior entry - resolved by keeping both entries in sequence (PR #7's entry, then PR #8's). No code conflicts. Gate results on the merged tree: build clean, 471 unit tests passed (1 skipped), lint 0 errors, e2e 15/15 passed (14 prior + firstLoginSecurity.spec.ts). Closing.

Merged into main at 67f6054 (merge commit onto PR #7, which landed first). One real conflict: PLAN.md dev-log entries from PR #7 and PR #8 both appended after the same prior entry - resolved by keeping both entries in sequence (PR #7's entry, then PR #8's). No code conflicts. Gate results on the merged tree: build clean, 471 unit tests passed (1 skipped), lint 0 errors, e2e 15/15 passed (14 prior + firstLoginSecurity.spec.ts). Closing.
claude closed this pull request 2026-09-16 18:38:21 +00:00
Some checks failed
CI / build-and-test (pull_request) Has been cancelled
CI / e2e (pull_request) Has been cancelled

Pull request closed

Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
thatumi/JorKonvo!8
No description provided.