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