Skip to content

Commit

Permalink
fix(auth, web): convert NativeError to FirebaseAuthError (#11258)
Browse files Browse the repository at this point in the history
* demonstrate that we cannot capture FirebaseError

* update example for testing

* fix(auth, web): get `FirebaseAuthError` from `NativeError` instance

* fix(auth, web): pass correct properties to FirebaseAuthException

* chore: remove print()

* Discard changes to packages/firebase_auth/firebase_auth/example/lib/main.dart

* chore: format

* chore: rm annotation

* chore: make dynamic type Object

* chore: make AuthErrorCustomData class

* chore: use AuthErrorCustomData class, update AuthError condition logic

* fix: use firebase Error code

* fix: use AuthError for interop

* format

* chore: update code comment

* fix: use window_interop.dart file to check Error constructor
  • Loading branch information
russellwheatley committed Jul 25, 2023
1 parent 199e1fc commit b95c380
Show file tree
Hide file tree
Showing 4 changed files with 70 additions and 20 deletions.
Expand Up @@ -246,7 +246,7 @@ external MultiFactorUserJsImpl multiFactor(
@JS()
external MultiFactorResolverJsImpl getMultiFactorResolver(
AuthJsImpl auth,
MultiFactorError error,
AuthError error,
);

@JS('Auth')
Expand Down Expand Up @@ -536,11 +536,14 @@ class UserProfile {
external factory UserProfile({String? displayName, String? photoURL});
}

@JS()
@staticInterop
abstract class AuthError {}

/// An authentication error.
///
/// See: <https://firebase.google.com/docs/reference/js/firebase.auth.Error>.
@JS('Error')
abstract class AuthError {
extension AuthErrorExtension on AuthError {
external String get code;
external set code(String s);
external String get message;
Expand All @@ -553,6 +556,18 @@ abstract class AuthError {
external set tenantId(String s);
external String get phoneNumber;
external set phoneNumber(String s);
external Object get customData;
}

@JS()
@staticInterop
class AuthErrorCustomData {}

extension AuthErrorCustomDataExtension on AuthErrorCustomData {
external String get appName;
external String? get email;
external String? get phoneNumber;
external String? get tenantId;
}

@JS()
Expand Down Expand Up @@ -707,13 +722,6 @@ class MultiFactorAssertionJsImpl {
external String get factorId;
}

/// https://firebase.google.com/docs/reference/js/auth.multifactorerror
@JS('Error')
@anonymous
class MultiFactorError extends AuthError {
external dynamic get customData;
}

/// https://firebase.google.com/docs/reference/js/auth.multifactorresolver
@JS()
@anonymous
Expand Down
Expand Up @@ -19,7 +19,7 @@ MultiFactorUser multiFactor(auth.User user) {

/// Given an AppJSImp, return the Auth instance.
MultiFactorResolver getMultiFactorResolver(
auth.Auth auth, auth_interop.MultiFactorError error) {
auth.Auth auth, auth.AuthError error) {
return MultiFactorResolver.fromJsObject(
auth_interop.getMultiFactorResolver(auth.jsObject, error));
}
Expand Down
@@ -0,0 +1,11 @@
// Copyright 2023 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

@JS()
library window_interop;

import 'package:js/js.dart';

@JS('Error')
external Object get errorConstructor;
Expand Up @@ -4,6 +4,7 @@
// BSD-style license that can be found in the LICENSE file.

import 'dart:io';
import 'package:js/js_util.dart';

import 'package:firebase_auth_platform_interface/firebase_auth_platform_interface.dart';
import 'package:firebase_auth_web/firebase_auth_web.dart';
Expand All @@ -13,6 +14,33 @@ import 'package:firebase_core_web/firebase_core_web_interop.dart'

import '../interop/auth.dart' as auth_interop;
import '../interop/multi_factor.dart' as multi_factor_interop;
import '../interop/window_interop.dart' as window_interop;

/// Workaround test to check whether `e` is practically a FirebaseError.
///
/// Ideally we'd check whether `e instanceof FirebaseError` however
/// there are two definitions of `FirebaseError` in deployed apps, one from
/// `firebase-auth.js` (which is usually minified) and one from
/// `firebase-app.js`.
///
/// Because they are not the same, the instanceof check fails. Instead, we test
/// that it is a window.Error (since that's the base class of FirebaseError) and
/// we check that it defines "customData" property which is on the AuthError but not the FirebaseError it extends
bool _isFirebaseAuthError(Object e) =>
instanceof(e, window_interop.errorConstructor) &&
hasProperty(e, 'customData');

bool _hasFirebaseAuthErrorCodeAndMessage(Object e) {
if (_isFirebaseAuthError(e)) {
String? code = getProperty(e, 'code');
String? message = getProperty(e, 'message');
if (code == null || !code.startsWith('auth/')) return false;
if (message == null || !message.contains('Firebase')) return false;
return true;
} else {
return false;
}
}

/// Given a web error, an [Exception] is returned.
///
Expand All @@ -22,20 +50,23 @@ FirebaseAuthException getFirebaseAuthException(
Object exception, [
auth_interop.Auth? auth,
]) {
if (exception is! core_interop.FirebaseError) {
if (!_hasFirebaseAuthErrorCodeAndMessage(exception)) {
return FirebaseAuthException(
code: 'unknown',
message: 'An unknown error occurred: $exception',
);
}

auth_interop.AuthError firebaseError = exception as auth_interop.AuthError;

String code = firebaseError.code.replaceFirst('auth/', '');
String message = firebaseError.message
.replaceFirst(' (${firebaseError.code}).', '')
.replaceFirst('Firebase: ', '');

// "customData" - see Firebase AuthError docs: https://firebase.google.com/docs/reference/js/auth.autherror
final customData =
getProperty(exception, 'customData') as auth_interop.AuthErrorCustomData;

if (code == 'multi-factor-auth-required') {
final _auth = auth;
if (_auth == null) {
Expand All @@ -47,15 +78,15 @@ FirebaseAuthException getFirebaseAuthException(
}
final resolverWeb = multi_factor_interop.getMultiFactorResolver(
_auth,
firebaseError as auth_interop.MultiFactorError,
exception,
);

return FirebaseAuthMultiFactorExceptionPlatform(
code: code,
message: message,
email: firebaseError.email,
phoneNumber: firebaseError.phoneNumber,
tenantId: firebaseError.tenantId,
email: customData.email,
phoneNumber: customData.phoneNumber,
tenantId: customData.tenantId,
resolver: MultiFactorResolverWeb(
resolverWeb.hints.map((e) {
if (e is multi_factor_interop.PhoneMultiFactorInfo) {
Expand Down Expand Up @@ -88,9 +119,9 @@ FirebaseAuthException getFirebaseAuthException(
return FirebaseAuthException(
code: code,
message: message,
email: firebaseError.email,
phoneNumber: firebaseError.phoneNumber,
tenantId: firebaseError.tenantId,
email: customData.email,
phoneNumber: customData.phoneNumber,
tenantId: customData.tenantId,
);
}

Expand Down

0 comments on commit b95c380

Please sign in to comment.