Skip to content

fix(flutter): audit bug fixes FR-25941/42/43/44#142

Merged
dianaKhortiuk-frontegg merged 10 commits into
masterfrom
combine/flutter-audit-bugs
Jul 24, 2026
Merged

fix(flutter): audit bug fixes FR-25941/42/43/44#142
dianaKhortiuk-frontegg merged 10 commits into
masterfrom
combine/flutter-audit-bugs

Conversation

@dianaKhortiuk-frontegg

@dianaKhortiuk-frontegg dianaKhortiuk-frontegg commented Jul 23, 2026

Copy link
Copy Markdown
Collaborator
  • Fixed: Android getConstants() omitted clientId, crashing FronteggConstants.fromMap on every call (FR-25941).
  • Fixed: activity-dependent methods hung the Dart Future when no Activity was attached; now complete with an error (FR-25943).
  • Fixed: login / directLogin / switchTenant reported success on failure or cancel; now reject/propagate the error (iOS + Android) (FR-25942).
  • Fixed: forceStateUpdate() never emitted state (Android no-op / iOS MissingPluginException); also fixed the iOS Combine listener leak on re-subscribe (FR-25944).
  • Bump native SDKs to Android 1.3.36 / iOS 1.3.12.

Supersedes #137, #138, #139, #140, #141.

Picks up the July 2026 mobile-SDK audit fixes shipped in the native
releases: iOS 1.3.12 (root-VC crash, refresh deadlock/race, passkey
login+registration completion, Sentry isolated hub) and Android 1.3.36
(main-thread blocking network, PKCE verifier length, process-death
callback, webview-bridge origin gate).
The Android getConstants() map omitted clientId, but Dart's
FronteggConstants.fromMap casts map["clientId"] as String (non-nullable),
so every Android getConstants() call threw a TypeError wrapped as
UnknownException. iOS already returns clientId.

- Add clientId to the constants map.
- Extract buildFronteggConstants(...) as a pure function over primitives so
  the key set (which must match the Dart contract) is unit-testable without
  mocking the final FronteggInnerStorage class.
- Remove the stale template FronteggFlutterPluginTest (referenced a removed
  plugin.onMethodCall / getPlatformVersion — never compiled, so the Android
  unit tests weren't runnable).

Test: FronteggConstantsTest asserts the map contains clientId (RED before,
GREEN after).
…943)

Eight activity-dependent methods (login, loginWithPasskeys, registerPasskeys,
stepUp, directLoginAction, directLogin, socialLogin, customSocialLogin) used
activityProvider.getActivity()?.let { } with no else branch. When no Activity
is attached (backgrounded app / config-change window) result was never invoked,
so the Dart Future hung forever with no error.

Route all eight through a shared withActivityOrError(...) guard that completes
result with a 'frontegg.error' when getActivity() is null (mirrors what
openAdminPortal already did). The guard is a top-level function so it's
unit-testable without constructing the handler (Context/Activity can't be
mocked in this toolchain).

Also removes the stale non-compiling FronteggFlutterPluginTest template.

Test: NoActivityHangTest (RED before, GREEN after).
…R-25942)

Android login/directLoginAction native callbacks are ((Exception?) -> Unit)?
and iOS login/directLoginAction/logout/switchTenant use Result callbacks, but
the plugin ignored the error and always called result.success(null). A cancelled
or failed authentication therefore surfaced to Dart as success.

- Android: add top-level completeAuthResult(error, result, onSuccess) that routes
  a non-null error through result.error(...) (FronteggException message as code,
  else "unknown"); wire login, directLoginAction, socialLogin fallback and
  customSocialLogin through it. logout ((): Unit) and switchTenant ((Boolean))
  already have no error / are handled. Unit-tested via AuthResultPropagationTest.
- iOS: replace the swallowing { _ in result(nil) } completion blocks in
  directLoginAction/directLogin/socialLogin/customSocialLogin, logout and
  switchTenant with the existing success/failure switch that maps FronteggError
  to a FlutterError (matching stepUp/login).
… (FR-25944)

forceStateUpdate() never delivered a refreshed state to Flutter, and on iOS it
threw MissingPluginException because the method had no handler case.

- Android: forceStateUpdate was a comment-only no-op that completed the result
  without notifying the state listener. Add forceNotifyChanges() to the
  FronteggStateListener interface and route forceStateUpdate through an extracted,
  unit-tested top-level forceStateUpdate(listener, result) that calls it.
- iOS: add the missing "forceStateUpdate" case and a handler that triggers a one-off
  state emit via the listener (wired into FronteggMethodCallHandler in register()).
- iOS: FronteggStateListenerImpl.subscribe() appended Combine sinks to cancellables
  without clearing prior ones, so every EventChannel onListen (e.g. hot restart)
  leaked sinks and fired updateState() once per accumulated sink. Call dispose()
  at the start of subscribe(). forceNotifyChanges() hops to a background queue
  before updateState() to avoid the DispatchQueue.main.sync deadlock when invoked
  from the main-thread method channel.

Test: ForceStateUpdateTest (Android, RED->GREEN).
…hang' into combine/flutter-audit-bugs

# Conflicts:
#	android/src/main/kotlin/com/frontegg/flutter/FronteggMethodCallHandler.kt
…s' into combine/flutter-audit-bugs

# Conflicts:
#	android/src/main/kotlin/com/frontegg/flutter/FronteggMethodCallHandler.kt
…into combine/flutter-audit-bugs

# Conflicts:
#	android/src/main/kotlin/com/frontegg/flutter/FronteggMethodCallHandler.kt
@dianaKhortiuk-frontegg

Copy link
Copy Markdown
Collaborator Author

Folded the native SDK bump (was #137) into this PR: android/build.gradlecom.frontegg.sdk:android:1.3.36, ios/frontegg_flutter/Package.swiftfrontegg-ios-swift exact 1.3.12. This PR now covers FR-25941/42/43/44 and the native bump.

@dianaKhortiuk-frontegg
dianaKhortiuk-frontegg merged commit 8dd5f8b into master Jul 24, 2026
12 checks passed
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