2FA challenge submission was inlined across the suite: `login.spec.ts`,
`login.smtp.spec.ts` and `setups/sso.ts:logUser` each asserted the
"Verify your Identity" heading, generated/retrieved the factor-specific
verification code, then clicked Continue. The only piece that varied was
the source of the code (TOTP generator vs. email-OTP retrieved from
maildev). When the web vault changes the heading copy, the code-input
label, or the Continue-button name, every duplicate has to be hunted
down separately.
Centralises the challenge flow in `setups/2fa.ts` behind a `TwoFactor`
discriminated union and a `submitTwoFactor` dispatcher:
type TwoFactor =
| { kind: 'totp', totp: OTPAuth.TOTP }
| { kind: 'mail2fa', mailBuffer: MailBuffer }
| { kind: 'fido2' };
Each variant carries exactly the state it needs. `submitTwoFactor`
asserts the heading then `switch`es on `kind`: TOTP fills the
next-period-boundary code (avoiding period-boundary expiry races near a
30-second tick) and mail2fa retrieves from the buffer; both then click
Continue. The `fido2` variant is declared so the union covers every
2FA provider the bundled web vault exposes (the provider row labelled
"FIDO2 WebAuthn" in en_GB / "Passkey" in en); no test currently drives
the webauthn-connector iframe / CDP virtual-authenticator handshake, so
the case throws `Not Implemented` rather than silently no-op'ing.
`setups/user.ts:logUser` and `setups/sso.ts:logUser` now share an
options bag `{ mailBuffer?, twoFactor? }` (the SSO variant's existing
positional `totp` parameter is replaced) and delegate to
`submitTwoFactor` when `twoFactor` is set, keeping the two login
helpers in lock-step.
Refactored consumers:
- `login.spec.ts:Authenticator 2fa` -> the inline 2FA block collapses to
`await logUser(test, page, user, { twoFactor: { kind: 'totp', totp } })`.
- `login.smtp.spec.ts:2fa` -> ditto with `{ kind: 'mail2fa', mailBuffer }`.
- `sso_login.spec.ts:SSO login with TOTP 2fa` -> same `totp` variant.
- `sso_login.smtp.spec.ts:Log and disable` -> same `mail2fa` variant.
- Positional-mailBuffer callers (`login.smtp.spec.ts:Login`,
`organization.smtp.spec.ts:Confirm invited user` and
`Organization is visible`) switch to options-bag
`logUser(..., { mailBuffer })`.
login.spec.ts loses no-longer-needed `expect` / `OTPAuth` imports; the
factor-specific timestamp logic moves into `submitTwoFactor`.
No behaviour change in any test; only structure.