From 31d653803082134ed222e8b7e32084fb39f34cc6 Mon Sep 17 00:00:00 2001 From: demolaf Date: Sat, 9 May 2026 00:57:09 +0100 Subject: [PATCH 1/2] fix(auth): make AuthException messages customisable via AuthUIStringProvider --- .../com/firebase/ui/auth/AuthException.kt | 78 +++++++++++++------ .../com/firebase/ui/auth/FirebaseAuthUI.kt | 4 +- .../AnonymousAuthProvider+FirebaseAuthUI.kt | 3 +- .../EmailAuthProvider+FirebaseAuthUI.kt | 8 +- .../FacebookAuthProvider+FirebaseAuthUI.kt | 8 +- .../GoogleAuthProvider+FirebaseAuthUI.kt | 6 +- .../OAuthProvider+FirebaseAuthUI.kt | 4 +- .../PhoneAuthProvider+FirebaseAuthUI.kt | 4 +- .../string_provider/AuthUIStringProvider.kt | 49 ++++++++++++ .../DefaultAuthUIStringProvider.kt | 45 +++++++++++ .../ui/auth/ui/screens/FirebaseAuthScreen.kt | 8 +- .../auth/ui/screens/email/EmailAuthScreen.kt | 6 +- .../auth/ui/screens/phone/PhoneAuthScreen.kt | 2 +- auth/src/main/res/values/strings.xml | 17 ++++ .../com/firebase/ui/auth/AuthExceptionTest.kt | 47 +++++++++++ 15 files changed, 238 insertions(+), 51 deletions(-) diff --git a/auth/src/main/java/com/firebase/ui/auth/AuthException.kt b/auth/src/main/java/com/firebase/ui/auth/AuthException.kt index 46d22f068..2b211f504 100644 --- a/auth/src/main/java/com/firebase/ui/auth/AuthException.kt +++ b/auth/src/main/java/com/firebase/ui/auth/AuthException.kt @@ -14,7 +14,10 @@ package com.firebase.ui.auth +import android.content.Context import com.firebase.ui.auth.AuthException.Companion.from +import com.firebase.ui.auth.configuration.string_provider.AuthUIStringProvider +import com.firebase.ui.auth.configuration.string_provider.DefaultAuthUIStringProvider import com.google.firebase.FirebaseException import com.google.firebase.auth.AuthCredential import com.google.firebase.auth.FirebaseAuthException @@ -341,15 +344,22 @@ abstract class AuthException( * @return An appropriate [AuthException] subtype */ @JvmStatic - fun from(firebaseException: Exception): AuthException { + fun from(firebaseException: Exception, context: Context): AuthException = + from(firebaseException, DefaultAuthUIStringProvider(context)) + + @JvmStatic + @JvmOverloads + fun from(firebaseException: Exception, stringProvider: AuthUIStringProvider? = null): AuthException { return when (firebaseException) { // If already an AuthException, return it directly is AuthException -> firebaseException - + // Handle specific Firebase Auth exceptions first (before general FirebaseException) is FirebaseAuthInvalidCredentialsException -> { InvalidCredentialsException( - message = firebaseException.message ?: "Invalid credentials provided", + message = stringProvider?.errorInvalidCredentials.nonEmpty() + ?: firebaseException.message + ?: "Invalid credentials provided", cause = firebaseException ) } @@ -357,17 +367,22 @@ abstract class AuthException( is FirebaseAuthInvalidUserException -> { when (firebaseException.errorCode) { "ERROR_USER_NOT_FOUND" -> UserNotFoundException( - message = firebaseException.message ?: "User not found", + message = stringProvider?.errorUserNotFound.nonEmpty() + ?: firebaseException.message + ?: "User not found", cause = firebaseException ) "ERROR_USER_DISABLED" -> InvalidCredentialsException( - message = firebaseException.message ?: "User account has been disabled", + message = stringProvider?.errorUserDisabled.nonEmpty() + ?: firebaseException.message.orEmpty(), cause = firebaseException ) else -> UserNotFoundException( - message = firebaseException.message ?: "User account error", + message = stringProvider?.errorUserAccountGeneric.nonEmpty() + ?: firebaseException.message + ?: "User account error", cause = firebaseException ) } @@ -375,7 +390,9 @@ abstract class AuthException( is FirebaseAuthWeakPasswordException -> { WeakPasswordException( - message = firebaseException.message ?: "Password is too weak", + message = stringProvider?.errorWeakPasswordGeneric.nonEmpty() + ?: firebaseException.message + ?: "Password is too weak", cause = firebaseException, reason = firebaseException.reason ) @@ -384,26 +401,31 @@ abstract class AuthException( is FirebaseAuthUserCollisionException -> { when (firebaseException.errorCode) { "ERROR_EMAIL_ALREADY_IN_USE" -> EmailAlreadyInUseException( - message = firebaseException.message + message = stringProvider?.errorEmailAlreadyInUse.nonEmpty() + ?: firebaseException.message ?: "Email address is already in use", cause = firebaseException, email = firebaseException.email ) "ERROR_ACCOUNT_EXISTS_WITH_DIFFERENT_CREDENTIAL" -> AccountLinkingRequiredException( - message = firebaseException.message + message = stringProvider?.errorAccountExistsDifferentCredential.nonEmpty() + ?: firebaseException.message ?: "Account already exists with different credentials", cause = firebaseException ) "ERROR_CREDENTIAL_ALREADY_IN_USE" -> AccountLinkingRequiredException( - message = firebaseException.message + message = stringProvider?.errorCredentialAlreadyInUse.nonEmpty() + ?: firebaseException.message ?: "Credential is already associated with a different user account", cause = firebaseException ) else -> AccountLinkingRequiredException( - message = firebaseException.message ?: "Account collision error", + message = stringProvider?.errorAccountCollisionGeneric.nonEmpty() + ?: firebaseException.message + ?: "Account collision error", cause = firebaseException ) } @@ -411,7 +433,8 @@ abstract class AuthException( is FirebaseAuthMultiFactorException -> { MfaRequiredException( - message = firebaseException.message + message = stringProvider?.errorMfaRequiredFallback.nonEmpty() + ?: firebaseException.message ?: "Multi-factor authentication required", cause = firebaseException ) @@ -419,23 +442,25 @@ abstract class AuthException( is FirebaseAuthRecentLoginRequiredException -> { InvalidCredentialsException( - message = firebaseException.message + message = stringProvider?.errorRecentLoginRequired.nonEmpty() + ?: firebaseException.message ?: "Recent login required for this operation", cause = firebaseException ) } is FirebaseAuthException -> { - // Handle FirebaseAuthException and check for specific error codes when (firebaseException.errorCode) { "ERROR_TOO_MANY_REQUESTS" -> TooManyRequestsException( - message = firebaseException.message + message = stringProvider?.errorTooManyRequests.nonEmpty() + ?: firebaseException.message ?: "Too many requests. Please try again later", cause = firebaseException ) else -> UnknownException( - message = firebaseException.message + message = stringProvider?.errorUnknownAuth.nonEmpty() + ?: firebaseException.message ?: "An unknown authentication error occurred", cause = firebaseException ) @@ -443,33 +468,36 @@ abstract class AuthException( } is FirebaseException -> { - // Handle general Firebase exceptions, which include network errors NetworkException( - message = firebaseException.message ?: "Network error occurred", + message = stringProvider?.errorNetworkGeneric.nonEmpty() + ?: firebaseException.message + ?: "Network error occurred", cause = firebaseException ) } else -> { - // Check for common cancellation patterns - if (firebaseException.message?.contains( - "cancelled", - ignoreCase = true - ) == true || + if (firebaseException.message?.contains("cancelled", ignoreCase = true) == true || firebaseException.message?.contains("canceled", ignoreCase = true) == true ) { AuthCancelledException( - message = firebaseException.message ?: "Authentication was cancelled", + message = stringProvider?.errorAuthCancelled.nonEmpty() + ?: firebaseException.message + ?: "Authentication was cancelled", cause = firebaseException ) } else { UnknownException( - message = firebaseException.message ?: "An unknown error occurred", + message = firebaseException.message + ?: stringProvider?.errorUnknownAuth.nonEmpty() + ?: "An unknown error occurred", cause = firebaseException ) } } } } + + private fun String?.nonEmpty(): String? = this?.ifEmpty { null } } } diff --git a/auth/src/main/java/com/firebase/ui/auth/FirebaseAuthUI.kt b/auth/src/main/java/com/firebase/ui/auth/FirebaseAuthUI.kt index 9f829a37f..a4d31f1a7 100644 --- a/auth/src/main/java/com/firebase/ui/auth/FirebaseAuthUI.kt +++ b/auth/src/main/java/com/firebase/ui/auth/FirebaseAuthUI.kt @@ -391,7 +391,7 @@ class FirebaseAuthUI private constructor( throw e } catch (e: Exception) { // Map to appropriate AuthException - val authException = AuthException.from(e) + val authException = AuthException.from(e, context) updateAuthState(AuthState.Error(authException)) throw authException } @@ -457,7 +457,7 @@ class FirebaseAuthUI private constructor( throw e } catch (e: Exception) { // Map to appropriate AuthException - val authException = AuthException.from(e) + val authException = AuthException.from(e, context) updateAuthState(AuthState.Error(authException)) throw authException } diff --git a/auth/src/main/java/com/firebase/ui/auth/configuration/auth_provider/AnonymousAuthProvider+FirebaseAuthUI.kt b/auth/src/main/java/com/firebase/ui/auth/configuration/auth_provider/AnonymousAuthProvider+FirebaseAuthUI.kt index 009765727..746537390 100644 --- a/auth/src/main/java/com/firebase/ui/auth/configuration/auth_provider/AnonymousAuthProvider+FirebaseAuthUI.kt +++ b/auth/src/main/java/com/firebase/ui/auth/configuration/auth_provider/AnonymousAuthProvider+FirebaseAuthUI.kt @@ -20,6 +20,7 @@ import kotlinx.coroutines.tasks.await */ @Composable internal fun FirebaseAuthUI.rememberAnonymousSignInHandler(): () -> Unit { + val context = androidx.compose.ui.platform.LocalContext.current val coroutineScope = rememberCoroutineScope() return remember(this) { { @@ -30,7 +31,7 @@ internal fun FirebaseAuthUI.rememberAnonymousSignInHandler(): () -> Unit { // Already an AuthException, don't re-wrap it updateAuthState(AuthState.Error(e)) } catch (e: Exception) { - val authException = AuthException.from(e) + val authException = AuthException.from(e, context) updateAuthState(AuthState.Error(authException)) } } diff --git a/auth/src/main/java/com/firebase/ui/auth/configuration/auth_provider/EmailAuthProvider+FirebaseAuthUI.kt b/auth/src/main/java/com/firebase/ui/auth/configuration/auth_provider/EmailAuthProvider+FirebaseAuthUI.kt index 8d4bae6d1..61b50c613 100644 --- a/auth/src/main/java/com/firebase/ui/auth/configuration/auth_provider/EmailAuthProvider+FirebaseAuthUI.kt +++ b/auth/src/main/java/com/firebase/ui/auth/configuration/auth_provider/EmailAuthProvider+FirebaseAuthUI.kt @@ -225,7 +225,7 @@ internal suspend fun FirebaseAuthUI.createOrLinkUserWithEmailAndPassword( updateAuthState(AuthState.Error(e)) throw e } catch (e: Exception) { - val authException = AuthException.from(e) + val authException = AuthException.from(e, context) updateAuthState(AuthState.Error(authException)) throw authException } @@ -450,7 +450,7 @@ internal suspend fun FirebaseAuthUI.signInWithEmailAndPassword( updateAuthState(AuthState.Error(e)) throw e } catch (e: Exception) { - val authException = AuthException.from(e) + val authException = AuthException.from(e, context) updateAuthState(AuthState.Error(authException)) throw authException } @@ -766,7 +766,7 @@ internal suspend fun FirebaseAuthUI.sendSignInLinkToEmail( updateAuthState(AuthState.Error(e)) throw e } catch (e: Exception) { - val authException = AuthException.from(e) + val authException = AuthException.from(e, context) updateAuthState(AuthState.Error(authException)) throw authException } @@ -987,7 +987,7 @@ internal suspend fun FirebaseAuthUI.signInWithEmailLink( updateAuthState(AuthState.Error(e)) throw e } catch (e: Exception) { - val authException = AuthException.from(e) + val authException = AuthException.from(e, context) updateAuthState(AuthState.Error(authException)) throw authException } diff --git a/auth/src/main/java/com/firebase/ui/auth/configuration/auth_provider/FacebookAuthProvider+FirebaseAuthUI.kt b/auth/src/main/java/com/firebase/ui/auth/configuration/auth_provider/FacebookAuthProvider+FirebaseAuthUI.kt index 28ef45636..c87748ea9 100644 --- a/auth/src/main/java/com/firebase/ui/auth/configuration/auth_provider/FacebookAuthProvider+FirebaseAuthUI.kt +++ b/auth/src/main/java/com/firebase/ui/auth/configuration/auth_provider/FacebookAuthProvider+FirebaseAuthUI.kt @@ -86,7 +86,7 @@ internal fun FirebaseAuthUI.rememberSignInWithFacebookLauncher( // Already an AuthException, don't re-wrap it updateAuthState(AuthState.Error(e)) } catch (e: Exception) { - val authException = AuthException.from(e) + val authException = AuthException.from(e, context) updateAuthState(AuthState.Error(authException)) } } @@ -98,7 +98,7 @@ internal fun FirebaseAuthUI.rememberSignInWithFacebookLauncher( override fun onError(error: FacebookException) { Log.e("FacebookAuthProvider", "Error during Facebook sign in", error) - val authException = AuthException.from(error) + val authException = AuthException.from(error, context) updateAuthState( AuthState.Error( authException @@ -190,7 +190,7 @@ internal suspend fun FirebaseAuthUI.signInWithFacebook( updateAuthState(AuthState.Error(e)) throw e } catch (e: FacebookException) { - val authException = AuthException.from(e) + val authException = AuthException.from(e, context) updateAuthState(AuthState.Error(authException)) throw authException } catch (e: CancellationException) { @@ -204,7 +204,7 @@ internal suspend fun FirebaseAuthUI.signInWithFacebook( updateAuthState(AuthState.Error(e)) throw e } catch (e: Exception) { - val authException = AuthException.from(e) + val authException = AuthException.from(e, context) updateAuthState(AuthState.Error(authException)) throw authException } 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 4d18cb0a9..f8cbbdddf 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 @@ -67,7 +67,7 @@ internal fun FirebaseAuthUI.rememberGoogleSignInHandler( } catch (e: AuthException) { updateAuthState(AuthState.Error(e)) } catch (e: Exception) { - val authException = AuthException.from(e) + val authException = AuthException.from(e, context) updateAuthState(AuthState.Error(authException)) } } @@ -128,7 +128,7 @@ internal suspend fun FirebaseAuthUI.signInWithGoogle( authorizationProvider.authorize(context, requestedScopes) } catch (e: Exception) { // Continue with sign-in even if scope authorization fails - val authException = AuthException.from(e) + val authException = AuthException.from(e, context) updateAuthState(AuthState.Error(authException)) } } @@ -227,7 +227,7 @@ internal suspend fun FirebaseAuthUI.signInWithGoogle( throw e } catch (e: Exception) { - val authException = AuthException.from(e) + val authException = AuthException.from(e, context) updateAuthState(AuthState.Error(authException)) throw authException } diff --git a/auth/src/main/java/com/firebase/ui/auth/configuration/auth_provider/OAuthProvider+FirebaseAuthUI.kt b/auth/src/main/java/com/firebase/ui/auth/configuration/auth_provider/OAuthProvider+FirebaseAuthUI.kt index 485065746..4053684d6 100644 --- a/auth/src/main/java/com/firebase/ui/auth/configuration/auth_provider/OAuthProvider+FirebaseAuthUI.kt +++ b/auth/src/main/java/com/firebase/ui/auth/configuration/auth_provider/OAuthProvider+FirebaseAuthUI.kt @@ -74,7 +74,7 @@ internal fun FirebaseAuthUI.rememberOAuthSignInHandler( } catch (e: AuthException) { updateAuthState(AuthState.Error(e)) } catch (e: Exception) { - val authException = AuthException.from(e) + val authException = AuthException.from(e, context) updateAuthState(AuthState.Error(authException)) } } @@ -231,7 +231,7 @@ internal suspend fun FirebaseAuthUI.signInWithProvider( throw e } catch (e: Exception) { - val authException = AuthException.from(e) + val authException = AuthException.from(e, context) updateAuthState(AuthState.Error(authException)) throw authException } diff --git a/auth/src/main/java/com/firebase/ui/auth/configuration/auth_provider/PhoneAuthProvider+FirebaseAuthUI.kt b/auth/src/main/java/com/firebase/ui/auth/configuration/auth_provider/PhoneAuthProvider+FirebaseAuthUI.kt index 0be8ee8fa..dd8662064 100644 --- a/auth/src/main/java/com/firebase/ui/auth/configuration/auth_provider/PhoneAuthProvider+FirebaseAuthUI.kt +++ b/auth/src/main/java/com/firebase/ui/auth/configuration/auth_provider/PhoneAuthProvider+FirebaseAuthUI.kt @@ -224,7 +224,7 @@ internal suspend fun FirebaseAuthUI.submitVerificationCode( updateAuthState(AuthState.Error(e)) throw e } catch (e: Exception) { - val authException = AuthException.from(e) + val authException = AuthException.from(e, context) updateAuthState(AuthState.Error(authException)) throw authException } @@ -334,7 +334,7 @@ internal suspend fun FirebaseAuthUI.signInWithPhoneAuthCredential( updateAuthState(AuthState.Error(e)) throw e } catch (e: Exception) { - val authException = AuthException.from(e) + val authException = AuthException.from(e, context) updateAuthState(AuthState.Error(authException)) throw authException } diff --git a/auth/src/main/java/com/firebase/ui/auth/configuration/string_provider/AuthUIStringProvider.kt b/auth/src/main/java/com/firebase/ui/auth/configuration/string_provider/AuthUIStringProvider.kt index a062debdd..bc7a8acdb 100644 --- a/auth/src/main/java/com/firebase/ui/auth/configuration/string_provider/AuthUIStringProvider.kt +++ b/auth/src/main/java/com/firebase/ui/auth/configuration/string_provider/AuthUIStringProvider.kt @@ -542,4 +542,53 @@ interface AuthUIStringProvider { /** Tooltip message shown when MFA is disabled */ val mfaDisabledTooltip: String + + // ============================================================================================= + // AuthException error messages + // ============================================================================================= + + /** Error when a user account has been disabled by an administrator. */ + val errorUserDisabled: String + + /** Error when provided credentials are invalid. Return empty to use the Firebase SDK message. */ + val errorInvalidCredentials: String + + /** Error when the user account does not exist. Return empty to use the Firebase SDK message. */ + val errorUserNotFound: String + + /** Generic error for unexpected user account issues. Return empty to use the Firebase SDK message. */ + val errorUserAccountGeneric: String + + /** Error when the password is too weak. Return empty to use the Firebase SDK message. */ + val errorWeakPasswordGeneric: String + + /** Error when the email address is already registered. Return empty to use the Firebase SDK message. */ + val errorEmailAlreadyInUse: String + + /** Error when an account already exists with a different sign-in method. Return empty to use the Firebase SDK message. */ + val errorAccountExistsDifferentCredential: String + + /** Error when a credential is already linked to another account. Return empty to use the Firebase SDK message. */ + val errorCredentialAlreadyInUse: String + + /** Generic error for account collision issues. Return empty to use the Firebase SDK message. */ + val errorAccountCollisionGeneric: String + + /** Error when multi-factor authentication is required. Return empty to use the Firebase SDK message. */ + val errorMfaRequiredFallback: String + + /** Error when the operation requires a recent sign-in. Return empty to use the Firebase SDK message. */ + val errorRecentLoginRequired: String + + /** Error when sign-in is blocked due to too many attempts. Return empty to use the Firebase SDK message. */ + val errorTooManyRequests: String + + /** Generic unknown authentication error. Return empty to use the Firebase SDK message. */ + val errorUnknownAuth: String + + /** Error for network failures during authentication. Return empty to use the Firebase SDK message. */ + val errorNetworkGeneric: String + + /** Error when authentication is cancelled. Return empty to use the Firebase SDK message. */ + val errorAuthCancelled: String } diff --git a/auth/src/main/java/com/firebase/ui/auth/configuration/string_provider/DefaultAuthUIStringProvider.kt b/auth/src/main/java/com/firebase/ui/auth/configuration/string_provider/DefaultAuthUIStringProvider.kt index 429d6d286..3d2b9772d 100644 --- a/auth/src/main/java/com/firebase/ui/auth/configuration/string_provider/DefaultAuthUIStringProvider.kt +++ b/auth/src/main/java/com/firebase/ui/auth/configuration/string_provider/DefaultAuthUIStringProvider.kt @@ -494,4 +494,49 @@ class DefaultAuthUIStringProvider( override val mfaDisabledTooltip: String get() = localizedContext.getString(R.string.fui_mfa_disabled_tooltip) + + override val errorUserDisabled: String + get() = localizedContext.getString(R.string.fui_error_user_disabled) + + override val errorInvalidCredentials: String + get() = localizedContext.getString(R.string.fui_error_invalid_credentials) + + override val errorUserNotFound: String + get() = localizedContext.getString(R.string.fui_error_user_not_found) + + override val errorUserAccountGeneric: String + get() = localizedContext.getString(R.string.fui_error_user_account_generic) + + override val errorWeakPasswordGeneric: String + get() = localizedContext.getString(R.string.fui_error_weak_password_generic) + + override val errorEmailAlreadyInUse: String + get() = localizedContext.getString(R.string.fui_error_email_already_in_use) + + override val errorAccountExistsDifferentCredential: String + get() = localizedContext.getString(R.string.fui_error_account_exists_different_credential) + + override val errorCredentialAlreadyInUse: String + get() = localizedContext.getString(R.string.fui_error_credential_already_in_use) + + override val errorAccountCollisionGeneric: String + get() = localizedContext.getString(R.string.fui_error_account_collision_generic) + + override val errorMfaRequiredFallback: String + get() = localizedContext.getString(R.string.fui_error_mfa_required_fallback) + + override val errorRecentLoginRequired: String + get() = localizedContext.getString(R.string.fui_error_recent_login_required) + + override val errorTooManyRequests: String + get() = localizedContext.getString(R.string.fui_error_too_many_requests) + + override val errorUnknownAuth: String + get() = localizedContext.getString(R.string.fui_error_unknown_auth) + + override val errorNetworkGeneric: String + get() = localizedContext.getString(R.string.fui_error_network_generic) + + override val errorAuthCancelled: String + get() = localizedContext.getString(R.string.fui_error_auth_cancelled) } diff --git a/auth/src/main/java/com/firebase/ui/auth/ui/screens/FirebaseAuthScreen.kt b/auth/src/main/java/com/firebase/ui/auth/ui/screens/FirebaseAuthScreen.kt index 5a065400c..98ecf5542 100644 --- a/auth/src/main/java/com/firebase/ui/auth/ui/screens/FirebaseAuthScreen.kt +++ b/auth/src/main/java/com/firebase/ui/auth/ui/screens/FirebaseAuthScreen.kt @@ -361,7 +361,7 @@ fun FirebaseAuthScreen( authUI.signOut(context) // Keep sign-in preference for "Continue as..." on next launch } catch (e: Exception) { - onSignInFailure(AuthException.from(e)) + onSignInFailure(AuthException.from(e, stringProvider)) } finally { pendingLinkingCredential.value = null pendingResolver.value = null @@ -442,7 +442,7 @@ fun FirebaseAuthScreen( onComplete = { navController.popBackStack() }, onSkip = { navController.popBackStack() }, onError = { exception -> - onSignInFailure(AuthException.from(exception)) + onSignInFailure(AuthException.from(exception, stringProvider)) } ) } else { @@ -467,7 +467,7 @@ fun FirebaseAuthScreen( navController.popBackStack() }, onError = { exception -> - onSignInFailure(AuthException.from(exception)) + onSignInFailure(AuthException.from(exception, stringProvider)) } ) } else { @@ -598,7 +598,7 @@ fun FirebaseAuthScreen( LaunchedEffect(errorState) { val exception = when (val throwable = errorState.exception) { is AuthException -> throwable - else -> AuthException.from(throwable) + else -> AuthException.from(throwable, stringProvider) } dialogController.showErrorDialog( diff --git a/auth/src/main/java/com/firebase/ui/auth/ui/screens/email/EmailAuthScreen.kt b/auth/src/main/java/com/firebase/ui/auth/ui/screens/email/EmailAuthScreen.kt index 2ebc2542f..3c04e8244 100644 --- a/auth/src/main/java/com/firebase/ui/auth/ui/screens/email/EmailAuthScreen.kt +++ b/auth/src/main/java/com/firebase/ui/auth/ui/screens/email/EmailAuthScreen.kt @@ -181,7 +181,7 @@ fun EmailAuthScreen( } is AuthState.Error -> { - val exception = AuthException.from(state.exception) + val exception = AuthException.from(state.exception, context) onError(exception) dialogController?.showErrorDialog( exception = exception, @@ -265,7 +265,7 @@ fun EmailAuthScreen( skipCredentialSave = isUsingRetrievedCredential ) } catch (e: Exception) { - onError(AuthException.from(e)) + onError(AuthException.from(e, context)) } } }, @@ -290,7 +290,7 @@ fun EmailAuthScreen( ) } } catch (e: Exception) { - onError(AuthException.from(e)) + onError(AuthException.from(e, context)) } } }, diff --git a/auth/src/main/java/com/firebase/ui/auth/ui/screens/phone/PhoneAuthScreen.kt b/auth/src/main/java/com/firebase/ui/auth/ui/screens/phone/PhoneAuthScreen.kt index fa6278976..c3af834fb 100644 --- a/auth/src/main/java/com/firebase/ui/auth/ui/screens/phone/PhoneAuthScreen.kt +++ b/auth/src/main/java/com/firebase/ui/auth/ui/screens/phone/PhoneAuthScreen.kt @@ -210,7 +210,7 @@ fun PhoneAuthScreen( } is AuthState.Error -> { - val exception = AuthException.from(state.exception) + val exception = AuthException.from(state.exception, context) onError(exception) // Show dialog for phone-specific errors using top-level controller diff --git a/auth/src/main/res/values/strings.xml b/auth/src/main/res/values/strings.xml index bb4b4e813..cc5cfa6b3 100644 --- a/auth/src/main/res/values/strings.xml +++ b/auth/src/main/res/values/strings.xml @@ -222,6 +222,23 @@ Additional verification required. Please complete multi-factor authentication. Account needs to be linked. Please try a different sign-in method. Authentication was cancelled. Please try again when ready. + + User account has been disabled + + + + + + + + + + + + + + Choose Authentication Method diff --git a/auth/src/test/java/com/firebase/ui/auth/AuthExceptionTest.kt b/auth/src/test/java/com/firebase/ui/auth/AuthExceptionTest.kt index 0b7b5bbbf..caa382bb1 100644 --- a/auth/src/test/java/com/firebase/ui/auth/AuthExceptionTest.kt +++ b/auth/src/test/java/com/firebase/ui/auth/AuthExceptionTest.kt @@ -14,11 +14,15 @@ package com.firebase.ui.auth +import com.firebase.ui.auth.configuration.string_provider.AuthUIStringProvider import com.google.common.truth.Truth.assertThat import com.google.firebase.FirebaseException import com.google.firebase.auth.FirebaseAuthException +import com.google.firebase.auth.FirebaseAuthInvalidUserException import org.junit.Test import org.junit.runner.RunWith +import org.mockito.Mockito.mock +import org.mockito.kotlin.whenever import org.robolectric.RobolectricTestRunner import org.robolectric.annotation.Config @@ -136,4 +140,47 @@ class AuthExceptionTest { // Assert assertThat(exception.email).isEqualTo(email) } + + // ============================================================================================= + // AuthUIStringProvider message customisation + // ============================================================================================= + + @Test + fun `from() uses string provider message when non-empty`() { + val firebaseException = mock(FirebaseAuthInvalidUserException::class.java) + whenever(firebaseException.errorCode).thenReturn("ERROR_USER_DISABLED") + whenever(firebaseException.message).thenReturn("Firebase: user disabled") + + val stringProvider = mock(AuthUIStringProvider::class.java) + whenever(stringProvider.errorUserDisabled).thenReturn("Custom: account disabled") + + val result = AuthException.from(firebaseException, stringProvider) + + assertThat(result.message).isEqualTo("Custom: account disabled") + } + + @Test + fun `from() falls back to Firebase message when string provider returns empty`() { + val firebaseException = mock(FirebaseAuthInvalidUserException::class.java) + whenever(firebaseException.errorCode).thenReturn("ERROR_USER_DISABLED") + whenever(firebaseException.message).thenReturn("Firebase: user disabled") + + val stringProvider = mock(AuthUIStringProvider::class.java) + whenever(stringProvider.errorUserDisabled).thenReturn("") + + val result = AuthException.from(firebaseException, stringProvider) + + assertThat(result.message).isEqualTo("Firebase: user disabled") + } + + @Test + fun `from() falls back to Firebase message when no string provider given`() { + val firebaseException = mock(FirebaseAuthInvalidUserException::class.java) + whenever(firebaseException.errorCode).thenReturn("ERROR_USER_DISABLED") + whenever(firebaseException.message).thenReturn("Firebase: user disabled") + + val result = AuthException.from(firebaseException) + + assertThat(result.message).isEqualTo("Firebase: user disabled") + } } \ No newline at end of file From 2d488c440c1a13d8e521344dc9c5e9d9f15c3886 Mon Sep 17 00:00:00 2001 From: demolaf Date: Mon, 11 May 2026 17:39:11 +0100 Subject: [PATCH 2/2] updates --- auth/src/main/java/com/firebase/ui/auth/AuthException.kt | 7 ++++--- .../firebase/ui/auth/ui/screens/email/EmailAuthScreen.kt | 6 +++--- .../firebase/ui/auth/ui/screens/phone/PhoneAuthScreen.kt | 2 +- 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/auth/src/main/java/com/firebase/ui/auth/AuthException.kt b/auth/src/main/java/com/firebase/ui/auth/AuthException.kt index 2b211f504..ae9d96e53 100644 --- a/auth/src/main/java/com/firebase/ui/auth/AuthException.kt +++ b/auth/src/main/java/com/firebase/ui/auth/AuthException.kt @@ -375,7 +375,8 @@ abstract class AuthException( "ERROR_USER_DISABLED" -> InvalidCredentialsException( message = stringProvider?.errorUserDisabled.nonEmpty() - ?: firebaseException.message.orEmpty(), + ?: firebaseException.message + ?: "User account has been disabled", cause = firebaseException ) @@ -488,8 +489,8 @@ abstract class AuthException( ) } else { UnknownException( - message = firebaseException.message - ?: stringProvider?.errorUnknownAuth.nonEmpty() + message = stringProvider?.errorUnknownAuth.nonEmpty() + ?: firebaseException.message ?: "An unknown error occurred", cause = firebaseException ) diff --git a/auth/src/main/java/com/firebase/ui/auth/ui/screens/email/EmailAuthScreen.kt b/auth/src/main/java/com/firebase/ui/auth/ui/screens/email/EmailAuthScreen.kt index 3c04e8244..62972d18c 100644 --- a/auth/src/main/java/com/firebase/ui/auth/ui/screens/email/EmailAuthScreen.kt +++ b/auth/src/main/java/com/firebase/ui/auth/ui/screens/email/EmailAuthScreen.kt @@ -181,7 +181,7 @@ fun EmailAuthScreen( } is AuthState.Error -> { - val exception = AuthException.from(state.exception, context) + val exception = AuthException.from(state.exception, stringProvider) onError(exception) dialogController?.showErrorDialog( exception = exception, @@ -265,7 +265,7 @@ fun EmailAuthScreen( skipCredentialSave = isUsingRetrievedCredential ) } catch (e: Exception) { - onError(AuthException.from(e, context)) + onError(AuthException.from(e, stringProvider)) } } }, @@ -290,7 +290,7 @@ fun EmailAuthScreen( ) } } catch (e: Exception) { - onError(AuthException.from(e, context)) + onError(AuthException.from(e, stringProvider)) } } }, diff --git a/auth/src/main/java/com/firebase/ui/auth/ui/screens/phone/PhoneAuthScreen.kt b/auth/src/main/java/com/firebase/ui/auth/ui/screens/phone/PhoneAuthScreen.kt index c3af834fb..26161da78 100644 --- a/auth/src/main/java/com/firebase/ui/auth/ui/screens/phone/PhoneAuthScreen.kt +++ b/auth/src/main/java/com/firebase/ui/auth/ui/screens/phone/PhoneAuthScreen.kt @@ -210,7 +210,7 @@ fun PhoneAuthScreen( } is AuthState.Error -> { - val exception = AuthException.from(state.exception, context) + val exception = AuthException.from(state.exception, stringProvider) onError(exception) // Show dialog for phone-specific errors using top-level controller