Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -109,8 +109,8 @@ class FronteggMethodCallHandler(
val data = call.argument<String>("data") ?: throw ArgumentNotFoundException("data")
if (context.fronteggAuth.isEmbeddedMode) {
activityProvider.getActivity()?.let {
context.fronteggAuth.directLoginAction(it, type, data) {
result.success(null)
context.fronteggAuth.directLoginAction(it, type, data) { error ->
completeAuthResult(error, result) { result.success(null) }
}
}
} else {
Expand Down Expand Up @@ -230,8 +230,8 @@ class FronteggMethodCallHandler(
activity = activity,
type = "social-login",
data = provider,
callback = {
result.success(null)
callback = { error ->
completeAuthResult(error, result) { result.success(null) }
}
)
}
Expand Down Expand Up @@ -260,8 +260,8 @@ class FronteggMethodCallHandler(
activity = activity,
type = "custom-social-login",
data = id,
callback = {
result.success(null)
callback = { error ->
completeAuthResult(error, result) { result.success(null) }
}
)
}
Expand Down Expand Up @@ -350,15 +350,8 @@ class FronteggMethodCallHandler(
context.fronteggAuth.login(
activity = it,
loginHint = loginHint,
callback = {
// Force state update after successful authentication
// This is especially important for hosted mode
GlobalScope.launch(Dispatchers.Main) {
// Trigger state listener to update Flutter
// The state listener will automatically detect changes
// and send updated state to Flutter
}
result.success(null)
callback = { error ->
completeAuthResult(error, result) { result.success(null) }
}
)
}
Expand Down Expand Up @@ -475,4 +468,28 @@ class FronteggMethodCallHandler(
AdminPortalActivity.open(activity)
result.success(null)
}
}
}

/**
* Routes an auth callback's optional error to the Flutter [result] (FR-25942). The native
* `login`/`directLoginAction` callbacks are `((Exception?) -> Unit)?`; the plugin used to ignore
* the error and always call `result.success(null)`, so a cancelled or failed authentication
* looked like success to Dart. On a non-null error the result is completed with `result.error(...)`;
* only when there is no error does [onSuccess] run. Top-level so it can be unit-tested without a
* Context/Activity (which can't be mocked in this toolchain).
*/
internal fun completeAuthResult(
error: Exception?,
result: MethodChannel.Result,
onSuccess: () -> Unit,
) {
if (error == null) {
onSuccess()
return
}
if (error is FronteggException) {
result.error(error.message ?: "unknown", error.message ?: "Authentication failed", null)
} else {
result.error("unknown", error.localizedMessage ?: "Authentication failed", null)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package com.frontegg.flutter

import com.frontegg.android.exceptions.FronteggException
import io.flutter.plugin.common.MethodChannel
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertFalse
import kotlin.test.assertNull
import kotlin.test.assertTrue

/**
* FR-25942: login/directLoginAction native callbacks are `((Exception?) -> Unit)?`, but the
* plugin ignored the `Exception?` and always called `result.success(null)`. A cancelled or
* failed authentication therefore looked like success to Dart. `completeAuthResult` must route
* a non-null error through `result.error(...)` and only call `onSuccess` when there is no error.
*/
internal class AuthResultPropagationTest {

private class RecordingResult : MethodChannel.Result {
var errorCode: String? = null
var errorMessage: String? = null
var successCalled = false
override fun success(result: Any?) { successCalled = true }
override fun error(errorCode: String, errorMessage: String?, errorDetails: Any?) {
this.errorCode = errorCode
this.errorMessage = errorMessage
}
override fun notImplemented() {}
}

@Test
fun nullError_callsOnSuccess_notError() {
val result = RecordingResult()
var onSuccessRan = false

completeAuthResult(null, result) { onSuccessRan = true }

assertTrue(onSuccessRan, "onSuccess must run when there is no error")
assertNull(result.errorCode, "result.error must not be called on success")
}

@Test
fun genericError_completesResultWithError_andSkipsOnSuccess() {
val result = RecordingResult()
var onSuccessRan = false

completeAuthResult(RuntimeException("boom"), result) { onSuccessRan = true }

assertFalse(onSuccessRan, "onSuccess must not run when the callback reports an error")
assertEquals("unknown", result.errorCode)
assertEquals("boom", result.errorMessage)
}

@Test
fun fronteggException_usesItsMessageAsErrorCode() {
val result = RecordingResult()

completeAuthResult(FronteggException("frontegg.error.mfa_required"), result) {}

assertEquals("frontegg.error.mfa_required", result.errorCode)
}
}

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -116,8 +116,13 @@ class FronteggMethodCallHandler {

let additionalQueryParams = arguments["additionalQueryParams"] as? [String: String] ?? [:]

let compelation: FronteggAuth.CompletionHandler = { _ in
result(nil)
let compelation: FronteggAuth.CompletionHandler = { res in
switch (res) {
case .success(_):
result(nil)
case .failure(let error):
result(FlutterError(code: error.failureReason ?? "unknown", message: error.localizedDescription, details: nil))
}
}

fronteggApp.auth.directLoginAction(
Expand Down Expand Up @@ -147,8 +152,13 @@ class FronteggMethodCallHandler {

let additionalQueryParams = arguments["additionalQueryParams"] as? [String: String] ?? [:]

let compelation: FronteggAuth.CompletionHandler = { _ in
result(nil)
let compelation: FronteggAuth.CompletionHandler = { res in
switch (res) {
case .success(_):
result(nil)
case .failure(let error):
result(FlutterError(code: error.failureReason ?? "unknown", message: error.localizedDescription, details: nil))
}
}

fronteggApp.auth.directLoginAction(
Expand Down Expand Up @@ -179,8 +189,13 @@ class FronteggMethodCallHandler {

let additionalQueryParams = arguments["additionalQueryParams"] as? [String: String] ?? [:]

let compelation: FronteggAuth.CompletionHandler = { _ in
result(nil)
let compelation: FronteggAuth.CompletionHandler = { res in
switch (res) {
case .success(_):
result(nil)
case .failure(let error):
result(FlutterError(code: error.failureReason ?? "unknown", message: error.localizedDescription, details: nil))
}
}

fronteggApp.auth.directLoginAction(
Expand Down Expand Up @@ -212,8 +227,13 @@ class FronteggMethodCallHandler {

let additionalQueryParams = arguments["additionalQueryParams"] as? [String: String] ?? [:]

let compelation: FronteggAuth.CompletionHandler = { _ in
result(nil)
let compelation: FronteggAuth.CompletionHandler = { res in
switch (res) {
case .success(_):
result(nil)
case .failure(let error):
result(FlutterError(code: error.failureReason ?? "unknown", message: error.localizedDescription, details: nil))
}
}

fronteggApp.auth.directLoginAction(
Expand Down Expand Up @@ -312,8 +332,13 @@ class FronteggMethodCallHandler {
}

private func logout(result: @escaping FlutterResult) {
fronteggApp.auth.logout() { _ in
result(nil)
fronteggApp.auth.logout() { res in
switch (res) {
case .success(_):
result(nil)
case .failure(let error):
result(FlutterError(code: error.failureReason ?? "unknown", message: error.localizedDescription, details: nil))
}
}
}

Expand All @@ -329,8 +354,13 @@ class FronteggMethodCallHandler {
return result(FlutterError(code: "MISSING_PARAMS", message: "Missing 'tenantId' argumant", details: nil))
}

fronteggApp.auth.switchTenant(tenantId: tenantId) { _ in
result(nil)
fronteggApp.auth.switchTenant(tenantId: tenantId) { res in
switch (res) {
case .success(_):
result(nil)
case .failure(let error):
result(FlutterError(code: error.failureReason ?? "unknown", message: error.localizedDescription, details: nil))
}
}
}

Expand Down
Loading