Skip to content
Merged
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 @@ -46,6 +46,7 @@ import kotlinx.coroutines.launch
* @param context Android context for DataStore access when saving credentials for linking
* @param config The [AuthUIConfiguration] containing authentication settings
* @param provider The [AuthProvider.Facebook] configuration with scopes and credential provider
* @param loginManagerProvider Provides logout operations to clear stale Facebook sessions
*
* @return A launcher function that starts the Facebook sign-in flow when invoked
*
Expand All @@ -56,6 +57,7 @@ internal fun FirebaseAuthUI.rememberSignInWithFacebookLauncher(
context: Context,
config: AuthUIConfiguration,
provider: AuthProvider.Facebook,
loginManagerProvider: AuthProvider.Facebook.LoginManagerProvider = AuthProvider.Facebook.DefaultLoginManagerProvider(),
): () -> Unit {
val coroutineScope = rememberCoroutineScope()
val callbackManager = remember { CallbackManager.Factory.create() }
Expand Down Expand Up @@ -114,6 +116,11 @@ internal fun FirebaseAuthUI.rememberSignInWithFacebookLauncher(
updateAuthState(
AuthState.Loading("Signing in with facebook...")
)
try {
(testLoginManagerProvider ?: loginManagerProvider).logOut()
} catch (e: Exception) {
Log.w("FacebookAuthProvider", "Failed to clear Facebook session before sign in", e)
}
launcher.launch(provider.scopes)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,11 @@ package com.firebase.ui.auth.configuration.auth_provider

import android.content.Context
import android.net.Uri
import androidx.compose.ui.test.junit4.createComposeRule
import androidx.test.core.app.ApplicationProvider
import com.facebook.AccessToken
import com.facebook.FacebookException
import com.facebook.FacebookSdk
import com.firebase.ui.auth.AuthException
import com.firebase.ui.auth.AuthState
import com.firebase.ui.auth.FirebaseAuthUI
Expand All @@ -40,6 +42,7 @@ import kotlinx.coroutines.flow.first
import kotlinx.coroutines.test.runTest
import org.junit.After
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mock
Expand All @@ -61,6 +64,9 @@ import org.robolectric.annotation.Config
@Config(manifest = Config.NONE)
class FacebookAuthProviderFirebaseAuthUITest {

@get:Rule
val composeTestRule = createComposeRule()

@Mock
private lateinit var mockFirebaseAuth: FirebaseAuth

Expand All @@ -78,6 +84,11 @@ class FacebookAuthProviderFirebaseAuthUITest {

applicationContext = ApplicationProvider.getApplicationContext()

FacebookSdk.setApplicationId("fake-app-id")
FacebookSdk.setClientToken("fake-client-token")
@Suppress("DEPRECATION")
FacebookSdk.sdkInitialize(applicationContext)

FirebaseApp.getApps(applicationContext).forEach { app ->
app.delete()
}
Expand All @@ -102,6 +113,84 @@ class FacebookAuthProviderFirebaseAuthUITest {
}
}

@Test
@Config(manifest = Config.NONE, qualifiers = "night")
fun `rememberSignInWithFacebookLauncher - calls logOut before launching to clear stale token`() {
val instance = FirebaseAuthUI.create(firebaseApp, mockFirebaseAuth)
val provider = AuthProvider.Facebook()
val config = authUIConfiguration {
context = applicationContext
providers {
provider(provider)
}
}

var launcher: (() -> Unit)? = null

composeTestRule.setContent {
launcher = instance.rememberSignInWithFacebookLauncher(
context = applicationContext,
config = config,
provider = provider,
loginManagerProvider = mockFBAuthCredentialProvider,
)
}

composeTestRule.runOnIdle {
try {
launcher?.invoke()
} catch (_: Exception) {
// launcher.launch() may throw in test environment — that's expected
}
}

verify(mockFBAuthCredentialProvider).logOut()
}

@Test
@Config(manifest = Config.NONE, qualifiers = "night")
fun `rememberSignInWithFacebookLauncher - does not propagate stale token logout failure`() {
val instance = FirebaseAuthUI.create(firebaseApp, mockFirebaseAuth)
val provider = AuthProvider.Facebook()
val config = authUIConfiguration {
context = applicationContext
providers {
provider(provider)
}
}
val logoutException = RuntimeException("logout failed")
doAnswer {
throw logoutException
}.whenever(mockFBAuthCredentialProvider).logOut()

var launcher: (() -> Unit)? = null
var thrownException: Exception? = null

composeTestRule.setContent {
launcher = instance.rememberSignInWithFacebookLauncher(
context = applicationContext,
config = config,
provider = provider,
loginManagerProvider = mockFBAuthCredentialProvider,
)
}

composeTestRule.runOnIdle {
try {
launcher?.invoke()
} catch (e: Exception) {
thrownException = e
}
}

var exceptionInChain: Throwable? = thrownException
while (exceptionInChain != null) {
assertThat(exceptionInChain).isNotEqualTo(logoutException)
exceptionInChain = exceptionInChain.cause
}
verify(mockFBAuthCredentialProvider).logOut()
}

@Test
@Config(manifest = Config.NONE, qualifiers = "night")
fun `signInWithFacebook - successful sign in signs user in and emits Success authState`() = runTest {
Expand Down
Loading