Skip to content

fix: ApiException null statusCode crash on 2FA#310

Merged
TaprootFreak merged 4 commits into
developfrom
fix/api-exception-null-status-code
May 8, 2026
Merged

fix: ApiException null statusCode crash on 2FA#310
TaprootFreak merged 4 commits into
developfrom
fix/api-exception-null-status-code

Conversation

@TaprootFreak
Copy link
Copy Markdown
Contributor

Summary

  • ApiException.fromJson() crashed with type 'Null' is not a subtype of type 'int' in type cast when the API error response body didn't contain a statusCode field (e.g. TFA_REQUIRED 403 responses)
  • Added httpStatusCode fallback parameter to fromJson() and pass response.statusCode from all 18 call sites
  • This fixes the KYC "Fehler beim Laden" screen that appeared instead of the 2FA verification step

Test plan

  • flutter analyze — no issues
  • flutter test — 182 tests passed
  • Manual: open KYC screen with account that requires 2FA → should show 2FA step instead of error

ApiException.fromJson() crashed with "type 'Null' is not a subtype of
type 'int' in type cast" when the API error response did not include a
statusCode field in the JSON body (e.g. TFA_REQUIRED 403 responses).

Add httpStatusCode parameter to fromJson() as fallback and pass
response.statusCode from all call sites.
KycLevelRequiredException and RegistrationRequiredException now receive
and apply the HTTP status code fallback, consistent with the parent
ApiException.fromJson() pattern.
@TaprootFreak TaprootFreak marked this pull request as ready for review May 8, 2026 13:52
…ices

- Add regression tests for ApiException.fromJson() covering null
  statusCode, httpStatusCode fallback, and subclass creation
- Migrate all authenticated services to throw ApiException instead of
  generic Exception: dfx_support_service, dfx_bank_account_service,
  dfx_blockchain_api_service, dfx_faucet_service, dfx_brokerbot_service
  (sell endpoints)
- Revert unrelated .gitignore change from previous commit
Replace generic Exception with ApiException.fromJson() in:
- real_unit_sell_payment_info_service (4 error paths)
- real_unit_buy_payment_info_service (catch-all else branch)
- real_unit_registration_service (completeRegistration)

All authenticated DFX API error responses now consistently use
ApiException with httpStatusCode fallback.
@TaprootFreak TaprootFreak merged commit 957117a into develop May 8, 2026
1 check passed
@TaprootFreak TaprootFreak deleted the fix/api-exception-null-status-code branch May 8, 2026 14:07
TaprootFreak added a commit that referenced this pull request May 12, 2026
> [!IMPORTANT]
> **Merge-blocked on DFXswiss/bitbox_flutter#11.** Without that SDK PR's
per-message scoped dedup + 60 s BLE read timeout, the EIP-712 sign
aborts at step 1→2 (BLE-layer retransmit corrupts the U2F HID frame
stream) and / or after the 10 s timeout fires mid-confirmation.
Sequence: merge #11 → tag `v0.0.3` → bump this PR's `pubspec.yaml` from
`v0.0.2` to `v0.0.3` → merge here.

## Summary
- `KycCubit` hoists the disclaimer + form (name/address) + EIP-712
13-step BitBox sign in front of every state past the email step.
Returning users at `level >= requiredLevel` previously dropped straight
into `KycCompleted` without ever touching the BitBox; the
hardware-wallet ceremony is the security gate, not the backend KYC
level. `_bitboxConfirmed` is per-`KycCubit` instance and resets on every
KYC entry, so each entry forces a fresh confirmation.
- `KycRegistrationSubmitCubit` treats every `ApiException` raised after
a successful EIP-712 sign as `RegistrationStatus.completed`. The user
has proven hardware-wallet control on the device — the backend's logical
reply (already registered, wallet linked to another account, merge
required, …) is informational. `KycCubit.checkKyc()` then resolves the
next state from the refreshed status, including the existing
`KycAccountMergeRequested` page when the wallet is bound to another DFX
user. Network / parse / sign errors raise non-`ApiException` types and
still surface as a `KycRegistrationSubmitFailure` SnackBar.
- `Eip712Signer._signTypedData` now throws when the signature comes back
empty or `'0x'`. The bitbox_flutter iOS bridge returns empty bytes when
the user cancels mid-sign or the BLE link drops; before this PR the
empty signature was POSTed and silently accepted as a successful sign.
- TFA handling: the cubit recognises both `statusCode == 403` and `code
== 'TFA_REQUIRED'` as the trigger for routing to the 2FA step (paired
with #310's `httpStatusCode` fallback so the code path is reachable).
- `checkKyc` is wrapped in a 30 s top-level timeout. The
email-auto-registration recursion has a one-shot guard so a backend that
does not bump the level after `registerEmail` cannot keep the loading
spinner alive forever. An `isClosed` guard after the API fetch in
`_runCheckKyc` prevents a slow response from overwriting a timeout
failure.
- `DFXAuthService`: removed the BitBox auth skip from #304 (no longer
needed after the v0.0.2 SDK fixes — see inline comment for context).
Added a 3 min sign timeout for the BitBox 13-step ceremony, 20 s HTTP
timeouts, and an empty-signature guard for personal-message signing.
- Email verification shows a localized BitBox sign hint
(`registerEmailVerificationBitboxSignHint`) below the confirm button
while the signature ceremony is running.

## Test plan
- [x] `flutter analyze` — no issues
- [x] `flutter test` — 188 tests pass
- [ ] Fresh wallet without RealUnit registration: email → disclaimer →
form → BitBox 13 fields + sign → ident
- [ ] Wallet already attached to the same RealUnit user, KYC level < 30:
disclaimer → form → BitBox 13 fields + sign → ident (backend's
already-registered reply tolerated, KYC continues)
- [ ] **Wallet attached to a different DFX user**: disclaimer → form →
BitBox 13 fields + sign → `KycAccountMergePage` ("Identität in anderem
Konto gefunden, Merge per Email bestätigen") instead of the generic
failure screen
- [ ] Returning user already at `level >= 30`: disclaimer + form +
BitBox sign first, then `KycCompleted` (no silent grant)
- [ ] Cancel the BitBox sign mid-ceremony → `Signature was empty`
SnackBar, no silent success
- [ ] BitBox not connected on submit → existing modal sheet pops (light
theme), reconnect → retry
- [ ] Backend response with `code: TFA_REQUIRED` → cubit emits
`KycSuccess(twoFa)` instead of `KycFailure`

---------

Co-authored-by: TaprootFreak <142087526+TaprootFreak@users.noreply.github.com>
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.

1 participant