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