fix(e2e): recover the 2FA single-use collision instead of re-submitting (BUNYIP-453) #446

Merged
nrupard merged 1 commit from fix/BUNYIP-453-totp-fill-collision-retry into main 2026-08-03 20:15:01 +02:00
Owner

BUNYIP-453 (relates BUNYIP-445). The actual root of login.spec's TOTP failure - a harness bug, not the secret.

Problem

fillTotpStep (e2e/lib/login.ts) documents a single-use collision: global.setup and login.spec both log in as the e2e account within one 30s TOTP step, so the second reuses a code the first consumed (BUNYIP-428), bunyip rejects it, and the intended handling is to wait out the step and retry with a fresh code.

That retry never runs. The fill used setInputValue, whose 5-attempt loop re-sets the field - but this field auto-submits on six digits (BUNYIP-331), so a rejected code re-renders an empty field and setInputValue re-submits the SAME rejected code 5x, then throws login field value did not stick after a DOM set (holds 0 chars, expected 6). That throw aborts fillTotpStep before the step-wait retry, and the 5 rejected submits burn the per-user TWO_FACTOR_VERIFY_FAILURES cap (5 / 15min). This is exactly why staging shows login.spec failing while global.setup (first login, no collision) passes.

Change

Fill the TOTP field with a single inline DOM-set per attempt (not setInputValue's re-submit loop): re-acquire the re-rendered field, one set + input/change dispatch (auto-submit fires), wait for navigation off /login/2fa; the fallback Verify click fires only if the digits are still in the field (no blank submit after a rejection re-render); on rejection wait out the step and loop to a fresh code; both attempts failing throws a clear "enrolled secret mismatch / forward clock skew" message. At most 2 verify submits total. setInputValue is unchanged and still used for the email/password fields.

Verification

  • tsc --noEmit (e2e, strict) passes.
  • Behavioural (reporter, on-server): needs the e2e account's correct enrolled 2FA secret restored first - a wrong secret rejects every code regardless of this fix. With the right secret, login.spec's collision now recovers via the fresh-code retry instead of throwing "value did not stick".

🤖 Generated with Claude Code

BUNYIP-453 (relates BUNYIP-445). The actual root of login.spec's TOTP failure - a harness bug, not the secret. ## Problem `fillTotpStep` (`e2e/lib/login.ts`) documents a single-use collision: `global.setup` and `login.spec` both log in as the e2e account within one 30s TOTP step, so the second reuses a code the first consumed (BUNYIP-428), bunyip rejects it, and the intended handling is to wait out the step and retry with a fresh code. That retry never runs. The fill used `setInputValue`, whose 5-attempt loop re-sets the field - but this field auto-submits on six digits (BUNYIP-331), so a rejected code re-renders an empty field and `setInputValue` re-submits the SAME rejected code 5x, then throws `login field value did not stick after a DOM set (holds 0 chars, expected 6)`. That throw aborts `fillTotpStep` before the step-wait retry, and the 5 rejected submits burn the per-user `TWO_FACTOR_VERIFY_FAILURES` cap (5 / 15min). This is exactly why staging shows `login.spec` failing while `global.setup` (first login, no collision) passes. ## Change Fill the TOTP field with a single inline DOM-set per attempt (not `setInputValue`'s re-submit loop): re-acquire the re-rendered field, one set + input/change dispatch (auto-submit fires), wait for navigation off /login/2fa; the fallback Verify click fires only if the digits are still in the field (no blank submit after a rejection re-render); on rejection wait out the step and loop to a fresh code; both attempts failing throws a clear "enrolled secret mismatch / forward clock skew" message. At most 2 verify submits total. `setInputValue` is unchanged and still used for the email/password fields. ## Verification - `tsc --noEmit` (e2e, strict) passes. - Behavioural (reporter, on-server): needs the e2e account's correct enrolled 2FA secret restored first - a wrong secret rejects every code regardless of this fix. With the right secret, login.spec's collision now recovers via the fresh-code retry instead of throwing "value did not stick". 🤖 Generated with [Claude Code](https://claude.com/claude-code)
fix(e2e): recover the 2FA single-use collision instead of re-submitting
All checks were successful
E2E PR gate / Install + reachability (no deployment secrets) (pull_request) Successful in 14s
Check / fmt + clippy + build + tests (pull_request) Successful in 11m25s
Create release / Create release from merged PR (pull_request) Has been skipped
de6291e65f
fillTotpStep filled the /login/2fa code via setInputValue, whose 5-attempt re-set loop re-submits the code each time (the input auto-submits on six digits, BUNYIP-331). A rejected code (a consumed same-step code when the e2e account logs in twice in a row, BUNYIP-428) re-renders an empty field, so setInputValue re-sets and re-submits the SAME rejected code five times and throws "value did not stick" - before the documented step-wait + fresh-code retry runs, and after burning five TWO_FACTOR_VERIFY_FAILURES against the 5/15min cap. That is why login.spec fails while global.setup (the first login, no collision) passes.

Fill the field with a single DOM-set per attempt instead: on rejection the outer loop waits out the 30s step and submits a fresh, unconsumed code; the fallback Verify click only fires when the digits are still in the field (never a blank submit after a re-render); both attempts failing throws a clear secret/clock message. setInputValue is unchanged and still used for the email/password fields.

#BUNYIP-453

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
nrupard scheduled this pull request to auto merge when all checks succeed 2026-08-03 20:08:21 +02:00
nrupard deleted branch fix/BUNYIP-453-totp-fill-collision-retry 2026-08-03 20:15:01 +02:00
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
psa-systems/bunyip!446
No description provided.