From 6fa4f00f2c061c33ed498d2c1ac59793a1fbd267 Mon Sep 17 00:00:00 2001 From: demolaf Date: Fri, 31 Jul 2026 16:30:29 +0100 Subject: [PATCH] fix(auth): log diagnostic warning when Google sign-in NoCredentialException fallback exhausts --- .../GoogleAuthProvider+FirebaseAuthUI.kt | 15 +++++ .../GoogleAuthProviderFirebaseAuthUITest.kt | 62 +++++++++++++++++++ 2 files changed, 77 insertions(+) diff --git a/auth/src/main/java/com/firebase/ui/auth/configuration/auth_provider/GoogleAuthProvider+FirebaseAuthUI.kt b/auth/src/main/java/com/firebase/ui/auth/configuration/auth_provider/GoogleAuthProvider+FirebaseAuthUI.kt index 89837df3e..36b18e495 100644 --- a/auth/src/main/java/com/firebase/ui/auth/configuration/auth_provider/GoogleAuthProvider+FirebaseAuthUI.kt +++ b/auth/src/main/java/com/firebase/ui/auth/configuration/auth_provider/GoogleAuthProvider+FirebaseAuthUI.kt @@ -158,6 +158,21 @@ internal suspend fun FirebaseAuthUI.signInWithGoogle( autoSelectEnabled = provider.autoSelectEnabled ) } catch (fallbackException: NoCredentialException) { + // Credential Manager doesn't distinguish "no account on device" from + // developer-side misconfiguration, so log the possible causes for + // debugging. Never surfaced to end users: the overwhelming majority + // hitting this genuinely have no account, and Firebase Console + // guidance would just confuse them. + Log.w( + "GoogleAuthProvider", + "No credential returned from Credential Manager after trying both " + + "authorized and all accounts. Possible causes: (1) no Google " + + "account on this device, (2) no Android OAuth client / SHA-1 " + + "registered for this app's package + signing certificate in the " + + "Firebase console, or (3) the Credential Manager Google ID " + + "provider is unavailable on this device.", + fallbackException + ) // No Google accounts available on device at all throw AuthException.UnknownException( message = "No Google accounts available.\n\nPlease add a Google account to your device and try again.", diff --git a/auth/src/test/java/com/firebase/ui/auth/configuration/auth_provider/GoogleAuthProviderFirebaseAuthUITest.kt b/auth/src/test/java/com/firebase/ui/auth/configuration/auth_provider/GoogleAuthProviderFirebaseAuthUITest.kt index 81f94b161..28466b0b7 100644 --- a/auth/src/test/java/com/firebase/ui/auth/configuration/auth_provider/GoogleAuthProviderFirebaseAuthUITest.kt +++ b/auth/src/test/java/com/firebase/ui/auth/configuration/auth_provider/GoogleAuthProviderFirebaseAuthUITest.kt @@ -15,8 +15,10 @@ package com.firebase.ui.auth.configuration.auth_provider import android.content.Context +import android.util.Log import androidx.core.net.toUri import androidx.credentials.CredentialManager +import androidx.credentials.exceptions.NoCredentialException import androidx.test.core.app.ApplicationProvider import com.firebase.ui.auth.AuthException import com.firebase.ui.auth.AuthState @@ -50,6 +52,7 @@ import org.mockito.kotlin.argumentCaptor import org.mockito.kotlin.eq import org.robolectric.RobolectricTestRunner import org.robolectric.annotation.Config +import org.robolectric.shadows.ShadowLog /** * Comprehensive unit tests for Google Sign-In provider methods in FirebaseAuthUI. @@ -437,6 +440,65 @@ class GoogleAuthProviderFirebaseAuthUITest { assertThat(errorState.exception).isInstanceOf(AuthException.UnknownException::class.java) } + @Test + fun `Sign in with Google when both credential attempts throw NoCredentialException logs diagnostic warning`() = runTest { + ShadowLog.clear() + val noCredentialException = NoCredentialException("No credential available") + + `when`( + mockCredentialManagerProvider.getGoogleCredential( + context = eq(applicationContext), + credentialManager = any(), + serverClientId = eq("test-client-id"), + filterByAuthorizedAccounts = eq(true), + autoSelectEnabled = eq(false) + ) + ).thenAnswer { throw noCredentialException } + + `when`( + mockCredentialManagerProvider.getGoogleCredential( + context = eq(applicationContext), + credentialManager = any(), + serverClientId = eq("test-client-id"), + filterByAuthorizedAccounts = eq(false), + autoSelectEnabled = eq(false) + ) + ).thenAnswer { throw noCredentialException } + + val instance = FirebaseAuthUI.create(firebaseApp, mockFirebaseAuth) + val googleProvider = AuthProvider.Google( + serverClientId = "test-client-id", + scopes = emptyList() + ) + val config = authUIConfiguration { + context = applicationContext + providers { + provider(googleProvider) + } + } + + try { + instance.signInWithGoogle( + context = applicationContext, + config = config, + provider = googleProvider, + authorizationProvider = mockAuthorizationProvider, + credentialManagerProvider = mockCredentialManagerProvider + ) + throw AssertionError("Expected exception to be thrown") + } catch (e: AuthException) { + // User-facing message stays generic - never mentions Firebase Console/SHA-1 + assertThat(e).isInstanceOf(AuthException.UnknownException::class.java) + assertThat(e.message).contains("No Google accounts available") + } + + // Diagnostic detail goes to Logcat only, for developers + val diagnosticLog = ShadowLog.getLogs().firstOrNull { + it.type == Log.WARN && it.tag == "GoogleAuthProvider" && it.msg.contains("SHA-1") + } + assertThat(diagnosticLog).isNotNull() + } + @Test fun `Sign in with Google when Firebase sign-in fails should throw AuthException`() = runTest { val mockCredential = mock(AuthCredential::class.java)