Skip to content

Commit c180522

Browse files
authored
feat: AuthException parsing for UI (#2222)
1 parent ed47c1d commit c180522

File tree

2 files changed

+480
-0
lines changed

2 files changed

+480
-0
lines changed
Lines changed: 341 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,341 @@
1+
/*
2+
* Copyright 2025 Google Inc. All Rights Reserved.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
5+
* in compliance with the License. You may obtain a copy of the License at
6+
*
7+
* http://www.apache.org/licenses/LICENSE-2.0
8+
*
9+
* Unless required by applicable law or agreed to in writing, software distributed under the
10+
* License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
11+
* express or implied. See the License for the specific language governing permissions and
12+
* limitations under the License.
13+
*/
14+
15+
package com.firebase.ui.auth.compose
16+
17+
import com.google.firebase.FirebaseException
18+
import com.google.firebase.auth.FirebaseAuthException
19+
import com.google.firebase.auth.FirebaseAuthInvalidCredentialsException
20+
import com.google.firebase.auth.FirebaseAuthInvalidUserException
21+
import com.google.firebase.auth.FirebaseAuthMultiFactorException
22+
import com.google.firebase.auth.FirebaseAuthRecentLoginRequiredException
23+
import com.google.firebase.auth.FirebaseAuthUserCollisionException
24+
import com.google.firebase.auth.FirebaseAuthWeakPasswordException
25+
26+
/**
27+
* Abstract base class representing all possible authentication exceptions in Firebase Auth UI.
28+
*
29+
* This class provides a unified exception hierarchy for authentication operations, allowing
30+
* for consistent error handling across the entire Auth UI system.
31+
*
32+
* Use the companion object [from] method to create specific exception instances from
33+
* Firebase authentication exceptions.
34+
*
35+
* **Example usage:**
36+
* ```kotlin
37+
* try {
38+
* // Perform authentication operation
39+
* } catch (firebaseException: Exception) {
40+
* val authException = AuthException.from(firebaseException)
41+
* when (authException) {
42+
* is AuthException.NetworkException -> {
43+
* // Handle network error
44+
* }
45+
* is AuthException.InvalidCredentialsException -> {
46+
* // Handle invalid credentials
47+
* }
48+
* // ... handle other exception types
49+
* }
50+
* }
51+
* ```
52+
*
53+
* @property message The detailed error message
54+
* @property cause The underlying [Throwable] that caused this exception
55+
*
56+
* @since 10.0.0
57+
*/
58+
abstract class AuthException(
59+
message: String,
60+
cause: Throwable? = null
61+
) : Exception(message, cause) {
62+
63+
/**
64+
* A network error occurred during the authentication operation.
65+
*
66+
* This exception is thrown when there are connectivity issues, timeouts,
67+
* or other network-related problems.
68+
*
69+
* @property message The detailed error message
70+
* @property cause The underlying [Throwable] that caused this exception
71+
*/
72+
class NetworkException(
73+
message: String,
74+
cause: Throwable? = null
75+
) : AuthException(message, cause)
76+
77+
/**
78+
* The provided credentials are not valid.
79+
*
80+
* This exception is thrown when the user provides incorrect login information,
81+
* such as wrong email/password combinations or malformed credentials.
82+
*
83+
* @property message The detailed error message
84+
* @property cause The underlying [Throwable] that caused this exception
85+
*/
86+
class InvalidCredentialsException(
87+
message: String,
88+
cause: Throwable? = null
89+
) : AuthException(message, cause)
90+
91+
/**
92+
* The user account does not exist.
93+
*
94+
* This exception is thrown when attempting to sign in with credentials
95+
* for a user that doesn't exist in the Firebase Auth system.
96+
*
97+
* @property message The detailed error message
98+
* @property cause The underlying [Throwable] that caused this exception
99+
*/
100+
class UserNotFoundException(
101+
message: String,
102+
cause: Throwable? = null
103+
) : AuthException(message, cause)
104+
105+
/**
106+
* The password provided is not strong enough.
107+
*
108+
* This exception is thrown when creating an account or updating a password
109+
* with a password that doesn't meet the security requirements.
110+
*
111+
* @property message The detailed error message
112+
* @property cause The underlying [Throwable] that caused this exception
113+
* @property reason The specific reason why the password is considered weak
114+
*/
115+
class WeakPasswordException(
116+
message: String,
117+
cause: Throwable? = null,
118+
val reason: String? = null
119+
) : AuthException(message, cause)
120+
121+
/**
122+
* An account with the given email already exists.
123+
*
124+
* This exception is thrown when attempting to create a new account with
125+
* an email address that is already registered.
126+
*
127+
* @property message The detailed error message
128+
* @property cause The underlying [Throwable] that caused this exception
129+
* @property email The email address that already exists
130+
*/
131+
class EmailAlreadyInUseException(
132+
message: String,
133+
cause: Throwable? = null,
134+
val email: String? = null
135+
) : AuthException(message, cause)
136+
137+
/**
138+
* Too many requests have been made to the server.
139+
*
140+
* This exception is thrown when the client has made too many requests
141+
* in a short period and needs to wait before making additional requests.
142+
*
143+
* @property message The detailed error message
144+
* @property cause The underlying [Throwable] that caused this exception
145+
*/
146+
class TooManyRequestsException(
147+
message: String,
148+
cause: Throwable? = null
149+
) : AuthException(message, cause)
150+
151+
/**
152+
* Multi-Factor Authentication is required to proceed.
153+
*
154+
* This exception is thrown when a user has MFA enabled and needs to
155+
* complete additional authentication steps.
156+
*
157+
* @property message The detailed error message
158+
* @property cause The underlying [Throwable] that caused this exception
159+
*/
160+
class MfaRequiredException(
161+
message: String,
162+
cause: Throwable? = null
163+
) : AuthException(message, cause)
164+
165+
/**
166+
* Account linking is required to complete sign-in.
167+
*
168+
* This exception is thrown when a user tries to sign in with a provider
169+
* that needs to be linked to an existing account.
170+
*
171+
* @property message The detailed error message
172+
* @property cause The underlying [Throwable] that caused this exception
173+
*/
174+
class AccountLinkingRequiredException(
175+
message: String,
176+
cause: Throwable? = null
177+
) : AuthException(message, cause)
178+
179+
/**
180+
* Authentication was cancelled by the user.
181+
*
182+
* This exception is thrown when the user cancels an authentication flow,
183+
* such as dismissing a sign-in dialog or backing out of the process.
184+
*
185+
* @property message The detailed error message
186+
* @property cause The underlying [Throwable] that caused this exception
187+
*/
188+
class AuthCancelledException(
189+
message: String,
190+
cause: Throwable? = null
191+
) : AuthException(message, cause)
192+
193+
/**
194+
* An unknown or unhandled error occurred.
195+
*
196+
* This exception is thrown for errors that don't match any of the specific
197+
* exception types or for unexpected system errors.
198+
*
199+
* @property message The detailed error message
200+
* @property cause The underlying [Throwable] that caused this exception
201+
*/
202+
class UnknownException(
203+
message: String,
204+
cause: Throwable? = null
205+
) : AuthException(message, cause)
206+
207+
companion object {
208+
/**
209+
* Creates an appropriate [AuthException] instance from a Firebase authentication exception.
210+
*
211+
* This method maps known Firebase exception types to their corresponding [AuthException]
212+
* subtypes, providing a consistent exception hierarchy for error handling.
213+
*
214+
* **Mapping:**
215+
* - [FirebaseException] → [NetworkException] (for network-related errors)
216+
* - [FirebaseAuthInvalidCredentialsException] → [InvalidCredentialsException]
217+
* - [FirebaseAuthInvalidUserException] → [UserNotFoundException]
218+
* - [FirebaseAuthWeakPasswordException] → [WeakPasswordException]
219+
* - [FirebaseAuthUserCollisionException] → [EmailAlreadyInUseException]
220+
* - [FirebaseAuthException] with ERROR_TOO_MANY_REQUESTS → [TooManyRequestsException]
221+
* - [FirebaseAuthMultiFactorException] → [MfaRequiredException]
222+
* - Other exceptions → [UnknownException]
223+
*
224+
* **Example:**
225+
* ```kotlin
226+
* try {
227+
* // Firebase auth operation
228+
* } catch (firebaseException: Exception) {
229+
* val authException = AuthException.from(firebaseException)
230+
* handleAuthError(authException)
231+
* }
232+
* ```
233+
*
234+
* @param firebaseException The Firebase exception to convert
235+
* @return An appropriate [AuthException] subtype
236+
*/
237+
@JvmStatic
238+
fun from(firebaseException: Exception): AuthException {
239+
return when (firebaseException) {
240+
// Handle specific Firebase Auth exceptions first (before general FirebaseException)
241+
is FirebaseAuthInvalidCredentialsException -> {
242+
InvalidCredentialsException(
243+
message = firebaseException.message ?: "Invalid credentials provided",
244+
cause = firebaseException
245+
)
246+
}
247+
is FirebaseAuthInvalidUserException -> {
248+
when (firebaseException.errorCode) {
249+
"ERROR_USER_NOT_FOUND" -> UserNotFoundException(
250+
message = firebaseException.message ?: "User not found",
251+
cause = firebaseException
252+
)
253+
"ERROR_USER_DISABLED" -> InvalidCredentialsException(
254+
message = firebaseException.message ?: "User account has been disabled",
255+
cause = firebaseException
256+
)
257+
else -> UserNotFoundException(
258+
message = firebaseException.message ?: "User account error",
259+
cause = firebaseException
260+
)
261+
}
262+
}
263+
is FirebaseAuthWeakPasswordException -> {
264+
WeakPasswordException(
265+
message = firebaseException.message ?: "Password is too weak",
266+
cause = firebaseException,
267+
reason = firebaseException.reason
268+
)
269+
}
270+
is FirebaseAuthUserCollisionException -> {
271+
when (firebaseException.errorCode) {
272+
"ERROR_EMAIL_ALREADY_IN_USE" -> EmailAlreadyInUseException(
273+
message = firebaseException.message ?: "Email address is already in use",
274+
cause = firebaseException,
275+
email = firebaseException.email
276+
)
277+
"ERROR_ACCOUNT_EXISTS_WITH_DIFFERENT_CREDENTIAL" -> AccountLinkingRequiredException(
278+
message = firebaseException.message ?: "Account already exists with different credentials",
279+
cause = firebaseException
280+
)
281+
"ERROR_CREDENTIAL_ALREADY_IN_USE" -> AccountLinkingRequiredException(
282+
message = firebaseException.message ?: "Credential is already associated with a different user account",
283+
cause = firebaseException
284+
)
285+
else -> AccountLinkingRequiredException(
286+
message = firebaseException.message ?: "Account collision error",
287+
cause = firebaseException
288+
)
289+
}
290+
}
291+
is FirebaseAuthMultiFactorException -> {
292+
MfaRequiredException(
293+
message = firebaseException.message ?: "Multi-factor authentication required",
294+
cause = firebaseException
295+
)
296+
}
297+
is FirebaseAuthRecentLoginRequiredException -> {
298+
InvalidCredentialsException(
299+
message = firebaseException.message ?: "Recent login required for this operation",
300+
cause = firebaseException
301+
)
302+
}
303+
is FirebaseAuthException -> {
304+
// Handle FirebaseAuthException and check for specific error codes
305+
when (firebaseException.errorCode) {
306+
"ERROR_TOO_MANY_REQUESTS" -> TooManyRequestsException(
307+
message = firebaseException.message ?: "Too many requests. Please try again later",
308+
cause = firebaseException
309+
)
310+
else -> UnknownException(
311+
message = firebaseException.message ?: "An unknown authentication error occurred",
312+
cause = firebaseException
313+
)
314+
}
315+
}
316+
is FirebaseException -> {
317+
// Handle general Firebase exceptions, which include network errors
318+
NetworkException(
319+
message = firebaseException.message ?: "Network error occurred",
320+
cause = firebaseException
321+
)
322+
}
323+
else -> {
324+
// Check for common cancellation patterns
325+
if (firebaseException.message?.contains("cancelled", ignoreCase = true) == true ||
326+
firebaseException.message?.contains("canceled", ignoreCase = true) == true) {
327+
AuthCancelledException(
328+
message = firebaseException.message ?: "Authentication was cancelled",
329+
cause = firebaseException
330+
)
331+
} else {
332+
UnknownException(
333+
message = firebaseException.message ?: "An unknown error occurred",
334+
cause = firebaseException
335+
)
336+
}
337+
}
338+
}
339+
}
340+
}
341+
}

0 commit comments

Comments
 (0)