@react-native-firebase/auth 16.2.0 → 16.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -3,6 +3,16 @@
3
3
  All notable changes to this project will be documented in this file.
4
4
  See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
5
5
 
6
+ ### [16.3.1](https://github.com/invertase/react-native-firebase/compare/v16.3.0...v16.3.1) (2022-10-28)
7
+
8
+ **Note:** Version bump only for package @react-native-firebase/auth
9
+
10
+ ## [16.3.0](https://github.com/invertase/react-native-firebase/compare/v16.2.0...v16.3.0) (2022-10-26)
11
+
12
+ ### Features
13
+
14
+ - **auth:** Add multi-factor support for the sign-in flow ([#6593](https://github.com/invertase/react-native-firebase/issues/6593)) ([3c64bf5](https://github.com/invertase/react-native-firebase/commit/3c64bf5987eec73c8cc5d3f9246c4c0185eb7718))
15
+
6
16
  ## [16.2.0](https://github.com/invertase/react-native-firebase/compare/v16.1.1...v16.2.0) (2022-10-23)
7
17
 
8
18
  **Note:** Version bump only for package @react-native-firebase/auth
@@ -2,6 +2,9 @@ import { describe, expect, it } from '@jest/globals';
2
2
 
3
3
  import auth, { firebase } from '../lib';
4
4
 
5
+ // @ts-ignore - We don't mind missing types here
6
+ import { NativeFirebaseError } from '../../app/lib/internal';
7
+
5
8
  describe('Auth', function () {
6
9
  describe('namespace', function () {
7
10
  it('accessible from firebase.app()', function () {
@@ -69,4 +72,50 @@ describe('Auth', function () {
69
72
  }
70
73
  });
71
74
  });
75
+
76
+ describe('getMultiFactorResolver', function () {
77
+ it('should return null if no resolver object is found', function () {
78
+ const unknownError = NativeFirebaseError.fromEvent(
79
+ {
80
+ code: 'unknown',
81
+ },
82
+ 'auth',
83
+ );
84
+ const actual = auth.getMultiFactorResolver(auth(), unknownError);
85
+ expect(actual).toBe(null);
86
+ });
87
+
88
+ it('should return null if resolver object is null', function () {
89
+ const unknownError = NativeFirebaseError.fromEvent(
90
+ {
91
+ code: 'unknown',
92
+ resolver: null,
93
+ },
94
+ 'auth',
95
+ );
96
+ const actual = auth.getMultiFactorResolver(firebase.app().auth(), unknownError);
97
+ expect(actual).toBe(null);
98
+ });
99
+
100
+ it('should return the resolver object if its found', function () {
101
+ const resolver = { session: '', hints: [] };
102
+ const errorWithResolver = NativeFirebaseError.fromEvent(
103
+ {
104
+ code: 'multi-factor-auth-required',
105
+ resolver,
106
+ },
107
+ 'auth',
108
+ );
109
+ const actual = auth.getMultiFactorResolver(firebase.app().auth(), errorWithResolver);
110
+ // Using expect(actual).toEqual(resolver) causes unexpected errors:
111
+ // You attempted to use "firebase.app('[DEFAULT]').appCheck" but this module could not be found.
112
+ expect(actual).not.toBeNull();
113
+ // @ts-ignore We know actual is not null
114
+ expect(actual.session).toEqual(resolver.session);
115
+ // @ts-ignore We know actual is not null
116
+ expect(actual.hints).toEqual(resolver.hints);
117
+ // @ts-ignore We know actual is not null
118
+ expect(actual._auth).not.toBeNull();
119
+ });
120
+ });
72
121
  });
@@ -21,6 +21,7 @@ import android.app.Activity;
21
21
  import android.net.Uri;
22
22
  import android.os.Parcel;
23
23
  import android.util.Log;
24
+ import androidx.annotation.NonNull;
24
25
  import com.facebook.react.bridge.Arguments;
25
26
  import com.facebook.react.bridge.Promise;
26
27
  import com.facebook.react.bridge.ReactApplicationContext;
@@ -42,6 +43,7 @@ import com.google.firebase.auth.FacebookAuthProvider;
42
43
  import com.google.firebase.auth.FirebaseAuth;
43
44
  import com.google.firebase.auth.FirebaseAuthException;
44
45
  import com.google.firebase.auth.FirebaseAuthInvalidCredentialsException;
46
+ import com.google.firebase.auth.FirebaseAuthMultiFactorException;
45
47
  import com.google.firebase.auth.FirebaseAuthProvider;
46
48
  import com.google.firebase.auth.FirebaseAuthSettings;
47
49
  import com.google.firebase.auth.FirebaseUser;
@@ -49,9 +51,17 @@ import com.google.firebase.auth.FirebaseUserMetadata;
49
51
  import com.google.firebase.auth.GetTokenResult;
50
52
  import com.google.firebase.auth.GithubAuthProvider;
51
53
  import com.google.firebase.auth.GoogleAuthProvider;
54
+ import com.google.firebase.auth.MultiFactorAssertion;
55
+ import com.google.firebase.auth.MultiFactorInfo;
56
+ import com.google.firebase.auth.MultiFactorResolver;
57
+ import com.google.firebase.auth.MultiFactorSession;
52
58
  import com.google.firebase.auth.OAuthProvider;
53
59
  import com.google.firebase.auth.PhoneAuthCredential;
60
+ import com.google.firebase.auth.PhoneAuthOptions;
54
61
  import com.google.firebase.auth.PhoneAuthProvider;
62
+ import com.google.firebase.auth.PhoneMultiFactorAssertion;
63
+ import com.google.firebase.auth.PhoneMultiFactorGenerator;
64
+ import com.google.firebase.auth.PhoneMultiFactorInfo;
55
65
  import com.google.firebase.auth.TwitterAuthProvider;
56
66
  import com.google.firebase.auth.UserInfo;
57
67
  import com.google.firebase.auth.UserProfileChangeRequest;
@@ -59,6 +69,8 @@ import io.invertase.firebase.common.ReactNativeFirebaseEvent;
59
69
  import io.invertase.firebase.common.ReactNativeFirebaseEventEmitter;
60
70
  import io.invertase.firebase.common.ReactNativeFirebaseModule;
61
71
  import io.invertase.firebase.common.SharedUtils;
72
+ import java.text.SimpleDateFormat;
73
+ import java.util.Date;
62
74
  import java.util.HashMap;
63
75
  import java.util.Iterator;
64
76
  import java.util.List;
@@ -72,6 +84,12 @@ import javax.annotation.Nullable;
72
84
 
73
85
  @SuppressWarnings({"ThrowableResultOfMethodCallIgnored", "JavaDoc"})
74
86
  class ReactNativeFirebaseAuthModule extends ReactNativeFirebaseModule {
87
+ // Easier to use would be Instant and DateTimeFormatter, but these are only available in API 26+
88
+ // According to https://stackoverflow.com/a/2202300 this is not the optimal solution, but we only
89
+ // get a unix timestamp so we can hardcode the timezone.
90
+ public static final SimpleDateFormat ISO_8601_FORMATTER =
91
+ new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ");
92
+
75
93
  private static final String TAG = "Auth";
76
94
  private static HashMap<String, FirebaseAuth.AuthStateListener> mAuthListeners = new HashMap<>();
77
95
  private static HashMap<String, FirebaseAuth.IdTokenListener> mIdTokenListeners = new HashMap<>();
@@ -81,6 +99,9 @@ class ReactNativeFirebaseAuthModule extends ReactNativeFirebaseModule {
81
99
  private PhoneAuthProvider.ForceResendingToken mForceResendingToken;
82
100
  private PhoneAuthCredential mCredential;
83
101
 
102
+ private final HashMap<String, MultiFactorResolver> mCachedResolvers = new HashMap<>();
103
+ private final HashMap<String, MultiFactorSession> mMultiFactorSessions = new HashMap<>();
104
+
84
105
  ReactNativeFirebaseAuthModule(ReactApplicationContext reactContext) {
85
106
  super(reactContext, TAG);
86
107
  }
@@ -120,6 +141,9 @@ class ReactNativeFirebaseAuthModule extends ReactNativeFirebaseModule {
120
141
  firebaseAuth.removeIdTokenListener(mAuthListener);
121
142
  idTokenListenerIterator.remove();
122
143
  }
144
+
145
+ mCachedResolvers.clear();
146
+ mMultiFactorSessions.clear();
123
147
  }
124
148
 
125
149
  /** Add a new auth state listener - if one doesn't exist already */
@@ -929,6 +953,228 @@ class ReactNativeFirebaseAuthModule extends ReactNativeFirebaseModule {
929
953
  }
930
954
  }
931
955
 
956
+ @ReactMethod
957
+ public void getSession(final String appName, final Promise promise) {
958
+ FirebaseApp firebaseApp = FirebaseApp.getInstance(appName);
959
+ FirebaseAuth firebaseAuth = FirebaseAuth.getInstance(firebaseApp);
960
+ firebaseAuth
961
+ .getCurrentUser()
962
+ .getMultiFactor()
963
+ .getSession()
964
+ .addOnCompleteListener(
965
+ task -> {
966
+ if (!task.isSuccessful()) {
967
+ rejectPromiseWithExceptionMap(promise, task.getException());
968
+ return;
969
+ }
970
+
971
+ final MultiFactorSession session = task.getResult();
972
+ final String sessionId = Integer.toString(session.hashCode());
973
+ mMultiFactorSessions.put(sessionId, session);
974
+
975
+ promise.resolve(sessionId);
976
+ });
977
+ }
978
+
979
+ @ReactMethod
980
+ public void verifyPhoneNumberWithMultiFactorInfo(
981
+ final String appName, final String hintUid, final String sessionKey, final Promise promise) {
982
+ final MultiFactorResolver resolver = mCachedResolvers.get(sessionKey);
983
+ if (resolver == null) {
984
+ // See https://firebase.google.com/docs/reference/node/firebase.auth.multifactorresolver for
985
+ // the error code
986
+ rejectPromiseWithCodeAndMessage(
987
+ promise,
988
+ "invalid-multi-factor-session",
989
+ "No resolver for session found. Is the session id correct?");
990
+ return;
991
+ }
992
+
993
+ MultiFactorInfo selectedHint = null;
994
+ for (MultiFactorInfo multiFactorInfo : resolver.getHints()) {
995
+ if (hintUid.equals(multiFactorInfo.getUid())) {
996
+ selectedHint = multiFactorInfo;
997
+ break;
998
+ }
999
+ }
1000
+
1001
+ if (selectedHint == null) {
1002
+ rejectPromiseWithCodeAndMessage(
1003
+ promise,
1004
+ "multi-factor-info-not-found",
1005
+ "The user does not have a second factor matching the identifier provided.");
1006
+ return;
1007
+ }
1008
+
1009
+ if (!PhoneMultiFactorGenerator.FACTOR_ID.equals(selectedHint.getFactorId())) {
1010
+ rejectPromiseWithCodeAndMessage(
1011
+ promise, "unknown", "Unsupported second factor. Only phone factors are supported.");
1012
+ return;
1013
+ }
1014
+
1015
+ final Activity activity = getCurrentActivity();
1016
+ final PhoneAuthOptions phoneAuthOptions =
1017
+ PhoneAuthOptions.newBuilder()
1018
+ .setActivity(activity)
1019
+ .setMultiFactorHint((PhoneMultiFactorInfo) selectedHint)
1020
+ .setTimeout(30L, TimeUnit.SECONDS)
1021
+ .setMultiFactorSession(resolver.getSession())
1022
+ .setCallbacks(
1023
+ new PhoneAuthProvider.OnVerificationStateChangedCallbacks() {
1024
+ @Override
1025
+ public void onCodeSent(
1026
+ @NonNull String verificationId,
1027
+ @NonNull PhoneAuthProvider.ForceResendingToken forceResendingToken) {
1028
+ promise.resolve(verificationId);
1029
+ }
1030
+
1031
+ @Override
1032
+ public void onVerificationCompleted(
1033
+ @NonNull PhoneAuthCredential phoneAuthCredential) {
1034
+ resolveMultiFactorCredential(phoneAuthCredential, sessionKey, promise);
1035
+ }
1036
+
1037
+ @Override
1038
+ public void onVerificationFailed(@NonNull FirebaseException e) {
1039
+ promiseRejectAuthException(promise, e);
1040
+ }
1041
+ })
1042
+ .build();
1043
+
1044
+ PhoneAuthProvider.verifyPhoneNumber(phoneAuthOptions);
1045
+ }
1046
+
1047
+ @ReactMethod
1048
+ public void verifyPhoneNumberForMultiFactor(
1049
+ final String appName,
1050
+ final String phoneNumber,
1051
+ final String sessionKey,
1052
+ final Promise promise) {
1053
+ final MultiFactorSession multiFactorSession = mMultiFactorSessions.get(sessionKey);
1054
+ if (multiFactorSession == null) {
1055
+ rejectPromiseWithCodeAndMessage(promise, "unknown", "can't find session for provided key");
1056
+ return;
1057
+ }
1058
+
1059
+ final PhoneAuthOptions phoneAuthOptions =
1060
+ PhoneAuthOptions.newBuilder()
1061
+ .setPhoneNumber(phoneNumber)
1062
+ .setActivity(getCurrentActivity())
1063
+ .setTimeout(30L, TimeUnit.SECONDS)
1064
+ .setMultiFactorSession(multiFactorSession)
1065
+ .requireSmsValidation(true)
1066
+ .setCallbacks(
1067
+ new PhoneAuthProvider.OnVerificationStateChangedCallbacks() {
1068
+ @Override
1069
+ public void onVerificationCompleted(
1070
+ @NonNull PhoneAuthCredential phoneAuthCredential) {
1071
+ // We can't handle this flow in the JS part if we want to be compatible with
1072
+ // the firebase-js-sdk. If we set the requireSmsValidation option to true
1073
+ // this code should not be executed.
1074
+ rejectPromiseWithCodeAndMessage(
1075
+ promise, "not-implemented", "This is currently not supported.");
1076
+ }
1077
+
1078
+ @Override
1079
+ public void onVerificationFailed(@NonNull FirebaseException e) {
1080
+ promiseRejectAuthException(promise, e);
1081
+ }
1082
+
1083
+ @Override
1084
+ public void onCodeSent(
1085
+ @NonNull String verificationId,
1086
+ @NonNull PhoneAuthProvider.ForceResendingToken forceResendingToken) {
1087
+ promise.resolve(verificationId);
1088
+ }
1089
+ })
1090
+ .build();
1091
+
1092
+ PhoneAuthProvider.verifyPhoneNumber(phoneAuthOptions);
1093
+ }
1094
+
1095
+ @ReactMethod
1096
+ public void finalizeMultiFactorEnrollment(
1097
+ final String appName,
1098
+ final String verificationId,
1099
+ final String verificationCode,
1100
+ @Nullable final String displayName,
1101
+ final Promise promise) {
1102
+ FirebaseApp firebaseApp = FirebaseApp.getInstance(appName);
1103
+ FirebaseAuth firebaseAuth = FirebaseAuth.getInstance(firebaseApp);
1104
+
1105
+ final PhoneAuthCredential phoneAuthCredential =
1106
+ PhoneAuthProvider.getCredential(verificationId, verificationCode);
1107
+ final PhoneMultiFactorAssertion assertion =
1108
+ PhoneMultiFactorGenerator.getAssertion(phoneAuthCredential);
1109
+ firebaseAuth
1110
+ .getCurrentUser()
1111
+ .getMultiFactor()
1112
+ .enroll(assertion, displayName)
1113
+ .addOnCompleteListener(
1114
+ task -> {
1115
+ if (!task.isSuccessful()) {
1116
+ rejectPromiseWithExceptionMap(promise, task.getException());
1117
+ }
1118
+
1119
+ // Need to reload user to make it all visible?
1120
+ promise.resolve("yes");
1121
+ });
1122
+ }
1123
+ /**
1124
+ * This method is intended to resolve a {@link PhoneAuthCredential} obtained through a
1125
+ * multi-factor authentication flow. A credential can either be obtained using:
1126
+ *
1127
+ * <ul>
1128
+ * <li>{@link PhoneAuthProvider#getCredential(String, String)}
1129
+ * <li>or {@link
1130
+ * com.google.firebase.auth.PhoneAuthProvider.OnVerificationStateChangedCallbacks#onVerificationCompleted(PhoneAuthCredential)}
1131
+ * </ul>
1132
+ *
1133
+ * @param authCredential
1134
+ * @param sessionKey An identifier for the session the flow belongs to. Used to look up the {@link
1135
+ * MultiFactorResolver}
1136
+ * @param promise
1137
+ */
1138
+ private void resolveMultiFactorCredential(
1139
+ final PhoneAuthCredential authCredential, final String sessionKey, final Promise promise) {
1140
+ final MultiFactorAssertion multiFactorAssertion =
1141
+ PhoneMultiFactorGenerator.getAssertion(authCredential);
1142
+
1143
+ final MultiFactorResolver resolver = mCachedResolvers.get(sessionKey);
1144
+ if (resolver == null) {
1145
+ rejectPromiseWithCodeAndMessage(
1146
+ promise,
1147
+ "invalid-multi-factor-session",
1148
+ "No resolver for session found. Is the session id correct?");
1149
+ return;
1150
+ }
1151
+
1152
+ resolver
1153
+ .resolveSignIn(multiFactorAssertion)
1154
+ .addOnCompleteListener(
1155
+ task -> {
1156
+ if (task.isSuccessful()) {
1157
+ AuthResult authResult = task.getResult();
1158
+ promiseWithAuthResult(authResult, promise);
1159
+ } else {
1160
+ promiseRejectAuthException(promise, task.getException());
1161
+ }
1162
+ });
1163
+ }
1164
+
1165
+ @ReactMethod
1166
+ public void resolveMultiFactorSignIn(
1167
+ final String appName,
1168
+ final String session,
1169
+ final String verificationId,
1170
+ final String verificationCode,
1171
+ final Promise promise) {
1172
+
1173
+ final PhoneAuthCredential credential =
1174
+ PhoneAuthProvider.getCredential(verificationId, verificationCode);
1175
+ resolveMultiFactorCredential(credential, session, promise);
1176
+ }
1177
+
932
1178
  @ReactMethod
933
1179
  public void confirmationResultConfirm(
934
1180
  String appName, final String verificationCode, final Promise promise) {
@@ -1641,6 +1887,7 @@ class ReactNativeFirebaseAuthModule extends ReactNativeFirebaseModule {
1641
1887
  authResultMap.putMap("user", userMap);
1642
1888
 
1643
1889
  promise.resolve(authResultMap);
1890
+
1644
1891
  } else {
1645
1892
  promiseNoUser(promise, true);
1646
1893
  }
@@ -1654,7 +1901,14 @@ class ReactNativeFirebaseAuthModule extends ReactNativeFirebaseModule {
1654
1901
  */
1655
1902
  private void promiseRejectAuthException(Promise promise, Exception exception) {
1656
1903
  WritableMap error = getJSError(exception);
1657
- rejectPromiseWithCodeAndMessage(promise, error.getString("code"), error.getString("message"));
1904
+ final String sessionId = error.getString("sessionId");
1905
+ final MultiFactorResolver multiFactorResolver = mCachedResolvers.get(sessionId);
1906
+ WritableMap resolverAsMap = Arguments.createMap();
1907
+ if (multiFactorResolver != null) {
1908
+ resolverAsMap = resolverToMap(sessionId, multiFactorResolver);
1909
+ }
1910
+ rejectPromiseWithCodeAndMessage(
1911
+ promise, error.getString("code"), error.getString("message"), resolverAsMap);
1658
1912
  }
1659
1913
 
1660
1914
  /**
@@ -1741,6 +1995,20 @@ class ReactNativeFirebaseAuthModule extends ReactNativeFirebaseModule {
1741
1995
  }
1742
1996
  }
1743
1997
 
1998
+ if (exception instanceof FirebaseAuthMultiFactorException) {
1999
+ final FirebaseAuthMultiFactorException multiFactorException =
2000
+ (FirebaseAuthMultiFactorException) exception;
2001
+ // Make sure the error code conforms to the Web API. See
2002
+ // https://firebase.google.com/docs/auth/web/multi-factor#signing_users_in_with_a_second_factor
2003
+ code = "MULTI_FACTOR_AUTH_REQUIRED";
2004
+ final MultiFactorResolver resolver = multiFactorException.getResolver();
2005
+ final String sessionId = Integer.toString(resolver.getSession().hashCode());
2006
+ mCachedResolvers.put(sessionId, resolver);
2007
+ // Passing around a resolver ReadableMap leads to issues when trying to send the data back by
2008
+ // calling Promise#reject. Building the map just before sending solves that issue.
2009
+ error.putString("sessionId", sessionId);
2010
+ }
2011
+
1744
2012
  if (code.equals("UNKNOWN")) {
1745
2013
  if (exception instanceof FirebaseAuthInvalidCredentialsException) {
1746
2014
  code = "INVALID_EMAIL";
@@ -1753,6 +2021,24 @@ class ReactNativeFirebaseAuthModule extends ReactNativeFirebaseModule {
1753
2021
  }
1754
2022
  }
1755
2023
 
2024
+ // Some message need to be rewritten to match error messages from the Web SDK
2025
+ switch (code) {
2026
+ case "ERROR_UNVERIFIED_EMAIL":
2027
+ message = "This operation requires a verified email.";
2028
+ break;
2029
+ case "ERROR_UNSUPPORTED_FIRST_FACTOR":
2030
+ message =
2031
+ "Enrolling a second factor or signing in with a multi-factor account requires sign-in"
2032
+ + " with a supported first factor.";
2033
+ break;
2034
+ case "ERROR_INVALID_PHONE_NUMBER":
2035
+ message =
2036
+ "The format of the phone number provided is incorrect. Please enter the phone number in"
2037
+ + " a format that can be parsed into E.164 format. E.164 phone numbers are written"
2038
+ + " in the format [+][country code][subscriber number including area code].";
2039
+ break;
2040
+ }
2041
+
1756
2042
  code = code.toLowerCase(Locale.ROOT).replace("error_", "").replace('_', '-');
1757
2043
  error.putString("code", code);
1758
2044
  error.putString("message", message);
@@ -1881,9 +2167,42 @@ class ReactNativeFirebaseAuthModule extends ReactNativeFirebaseModule {
1881
2167
  }
1882
2168
  userMap.putMap("metadata", metadataMap);
1883
2169
 
2170
+ final WritableArray enrolledFactors = Arguments.createArray();
2171
+ for (final MultiFactorInfo hint : user.getMultiFactor().getEnrolledFactors()) {
2172
+ enrolledFactors.pushMap(multiFactorInfoToMap(hint));
2173
+ }
2174
+ final WritableMap multiFactorMap = Arguments.createMap();
2175
+ multiFactorMap.putArray("enrolledFactors", enrolledFactors);
2176
+ userMap.putMap("multiFactor", multiFactorMap);
2177
+
1884
2178
  return userMap;
1885
2179
  }
1886
2180
 
2181
+ @NonNull
2182
+ private WritableMap resolverToMap(final String sessionId, final MultiFactorResolver resolver) {
2183
+ final WritableMap result = Arguments.createMap();
2184
+ final WritableArray hints = Arguments.createArray();
2185
+ for (MultiFactorInfo hint : resolver.getHints()) {
2186
+ hints.pushMap(multiFactorInfoToMap(hint));
2187
+ }
2188
+
2189
+ result.putArray("hints", hints);
2190
+ result.putString("session", sessionId);
2191
+ return result;
2192
+ }
2193
+
2194
+ @NonNull
2195
+ private WritableMap multiFactorInfoToMap(MultiFactorInfo hint) {
2196
+ final WritableMap hintMap = Arguments.createMap();
2197
+ final Date enrollmentTime = new Date(hint.getEnrollmentTimestamp() * 1000);
2198
+ hintMap.putString("displayName", hint.getDisplayName());
2199
+ hintMap.putString("enrollmentTime", ISO_8601_FORMATTER.format(enrollmentTime));
2200
+ hintMap.putString("factorId", hint.getFactorId());
2201
+ hintMap.putString("uid", hint.getUid());
2202
+
2203
+ return hintMap;
2204
+ }
2205
+
1887
2206
  private ActionCodeSettings buildActionCodeSettings(ReadableMap actionCodeSettings) {
1888
2207
  ActionCodeSettings.Builder builder = ActionCodeSettings.newBuilder();
1889
2208
 
@@ -82,4 +82,5 @@ NSString* const AuthErrorCode_toJSErrorCode[] = {
82
82
  [FIRAuthErrorCodeNullUser] = @"null-user",
83
83
  [FIRAuthErrorCodeKeychainError] = @"keychain-error",
84
84
  [FIRAuthErrorCodeInternalError] = @"internal-error",
85
- [FIRAuthErrorCodeMalformedJWT] = @"malformed-jwt"};
85
+ [FIRAuthErrorCodeMalformedJWT] = @"malformed-jwt",
86
+ [FIRAuthErrorCodeSecondFactorRequired] = @"multi-factor-auth-required"};
@@ -31,6 +31,7 @@ static NSString *const keyAndroid = @"android";
31
31
  static NSString *const keyProfile = @"profile";
32
32
  static NSString *const keyNewUser = @"isNewUser";
33
33
  static NSString *const keyUsername = @"username";
34
+ static NSString *const keyMultiFactor = @"multiFactor";
34
35
  static NSString *const keyPhotoUrl = @"photoURL";
35
36
  static NSString *const keyBundleId = @"bundleId";
36
37
  static NSString *const keyInstallApp = @"installApp";
@@ -53,6 +54,8 @@ static __strong NSMutableDictionary *idTokenHandlers;
53
54
  static __strong NSMutableDictionary *emulatorConfigs;
54
55
  // Used for caching credentials between method calls.
55
56
  static __strong NSMutableDictionary<NSString *, FIRAuthCredential *> *credentials;
57
+ static __strong NSMutableDictionary<NSString *, FIRMultiFactorResolver *> *cachedResolver;
58
+ static __strong NSMutableDictionary<NSString *, FIRMultiFactorSession *> *cachedSessions;
56
59
 
57
60
  @implementation RNFBAuthModule
58
61
  #pragma mark -
@@ -72,6 +75,8 @@ RCT_EXPORT_MODULE();
72
75
  idTokenHandlers = [[NSMutableDictionary alloc] init];
73
76
  emulatorConfigs = [[NSMutableDictionary alloc] init];
74
77
  credentials = [[NSMutableDictionary alloc] init];
78
+ cachedResolver = [[NSMutableDictionary alloc] init];
79
+ cachedSessions = [[NSMutableDictionary alloc] init];
75
80
  });
76
81
  return self;
77
82
  }
@@ -97,6 +102,8 @@ RCT_EXPORT_MODULE();
97
102
  [idTokenHandlers removeAllObjects];
98
103
 
99
104
  [credentials removeAllObjects];
105
+ [cachedResolver removeAllObjects];
106
+ [cachedSessions removeAllObjects];
100
107
  }
101
108
 
102
109
  #pragma mark -
@@ -734,12 +741,146 @@ RCT_EXPORT_METHOD(signInWithPhoneNumber
734
741
  }
735
742
  }];
736
743
  }
744
+ RCT_EXPORT_METHOD(verifyPhoneNumberWithMultiFactorInfo
745
+ : (FIRApp *)firebaseApp
746
+ : (NSString *)hintUid
747
+ : (NSString *)sessionKey
748
+ : (RCTPromiseResolveBlock)resolve
749
+ : (RCTPromiseRejectBlock)reject) {
750
+ if ([cachedResolver valueForKey:sessionKey] == nil) {
751
+ [RNFBSharedUtils
752
+ rejectPromiseWithUserInfo:reject
753
+ userInfo:(NSMutableDictionary *)@{
754
+ @"code" : @"invalid-multi-factor-session",
755
+ @"message" : @"No resolver for session found. Is the session id correct?"
756
+ }];
757
+ return;
758
+ }
759
+ FIRMultiFactorSession *session = cachedResolver[sessionKey].session;
760
+ NSPredicate *findByUid = [NSPredicate predicateWithFormat:@"UID == %@", hintUid];
761
+ FIRMultiFactorInfo *_Nullable hint =
762
+ [[cachedResolver[sessionKey].hints filteredArrayUsingPredicate:findByUid] firstObject];
763
+ if (hint == nil) {
764
+ [RNFBSharedUtils rejectPromiseWithUserInfo:reject
765
+ userInfo:(NSMutableDictionary *)@{
766
+ @"code" : @"multi-factor-info-not-found",
767
+ @"message" : @"The user does not have a second factor "
768
+ @"matching the identifier provided."
769
+ }];
770
+ return;
771
+ }
772
+
773
+ [FIRPhoneAuthProvider.provider
774
+ verifyPhoneNumberWithMultiFactorInfo:hint
775
+ UIDelegate:nil
776
+ multiFactorSession:session
777
+ completion:^(NSString *_Nullable verificationID,
778
+ NSError *_Nullable error) {
779
+ if (error) {
780
+ [self promiseRejectAuthException:reject error:error];
781
+ } else {
782
+ resolve(verificationID);
783
+ }
784
+ }];
785
+ }
786
+
787
+ RCT_EXPORT_METHOD(verifyPhoneNumberForMultiFactor
788
+ : (FIRApp *)firebaseApp
789
+ : (NSString *)phoneNumber
790
+ : (NSString *)sessionId
791
+ : (RCTPromiseResolveBlock)resolve
792
+ : (RCTPromiseRejectBlock)reject) {
793
+ FIRMultiFactorSession *session = cachedSessions[sessionId];
794
+ [FIRPhoneAuthProvider.provider
795
+ verifyPhoneNumber:phoneNumber
796
+ UIDelegate:nil
797
+ multiFactorSession:session
798
+ completion:^(NSString *_Nullable verificationID, NSError *_Nullable error) {
799
+ if (error != nil) {
800
+ [self promiseRejectAuthException:reject error:error];
801
+ return;
802
+ }
803
+
804
+ resolve(verificationID);
805
+ }];
806
+ }
807
+
808
+ RCT_EXPORT_METHOD(resolveMultiFactorSignIn
809
+ : (FIRApp *)firebaseApp
810
+ : (NSString *)sessionKey
811
+ : (NSString *)verificationId
812
+ : (NSString *)verificationCode
813
+ : (RCTPromiseResolveBlock)resolve
814
+ : (RCTPromiseRejectBlock)reject) {
815
+ FIRPhoneAuthCredential *credential =
816
+ [[FIRPhoneAuthProvider providerWithAuth:[FIRAuth authWithApp:firebaseApp]]
817
+ credentialWithVerificationID:verificationId
818
+ verificationCode:verificationCode];
819
+ FIRMultiFactorAssertion *assertion =
820
+ [FIRPhoneMultiFactorGenerator assertionWithCredential:credential];
821
+ [cachedResolver[sessionKey] resolveSignInWithAssertion:assertion
822
+ completion:^(FIRAuthDataResult *_Nullable authResult,
823
+ NSError *_Nullable error) {
824
+ if (error) {
825
+ [self promiseRejectAuthException:reject
826
+ error:error];
827
+ } else {
828
+ [self promiseWithAuthResult:resolve
829
+ rejecter:reject
830
+ authResult:authResult];
831
+ }
832
+ }];
833
+ }
834
+
835
+ RCT_EXPORT_METHOD(getSession
836
+ : (FIRApp *)firebaseApp
837
+ : (RCTPromiseResolveBlock)resolve
838
+ : (RCTPromiseRejectBlock)reject) {
839
+ FIRUser *user = [FIRAuth authWithApp:firebaseApp].currentUser;
840
+ [[user multiFactor] getSessionWithCompletion:^(FIRMultiFactorSession *_Nullable session,
841
+ NSError *_Nullable error) {
842
+ if (error != nil) {
843
+ [self promiseRejectAuthException:reject error:error];
844
+ return;
845
+ }
846
+
847
+ NSString *sessionId = [NSString stringWithFormat:@"%@", @([session hash])];
848
+ cachedSessions[sessionId] = session;
849
+ resolve(sessionId);
850
+ }];
851
+ }
852
+
853
+ RCT_EXPORT_METHOD(finalizeMultiFactorEnrollment
854
+ : (FIRApp *)firebaseApp
855
+ : (NSString *)verificationId
856
+ : (NSString *)verificationCode
857
+ : (NSString *_Nullable)displayName
858
+ : (RCTPromiseResolveBlock)resolve
859
+ : (RCTPromiseRejectBlock)reject) {
860
+ FIRPhoneAuthCredential *credential =
861
+ [FIRPhoneAuthProvider.provider credentialWithVerificationID:verificationId
862
+ verificationCode:verificationCode];
863
+ FIRMultiFactorAssertion *assertion =
864
+ [FIRPhoneMultiFactorGenerator assertionWithCredential:credential];
865
+ FIRUser *user = [FIRAuth authWithApp:firebaseApp].currentUser;
866
+ [user.multiFactor enrollWithAssertion:assertion
867
+ displayName:displayName
868
+ completion:^(NSError *_Nullable error) {
869
+ if (error != nil) {
870
+ [self promiseRejectAuthException:reject error:error];
871
+ return;
872
+ }
873
+
874
+ resolve(nil);
875
+ return;
876
+ }];
877
+ }
737
878
 
738
879
  RCT_EXPORT_METHOD(verifyPhoneNumber
739
880
  : (FIRApp *)firebaseApp
740
881
  : (NSString *)phoneNumber
741
882
  : (NSString *)requestKey) {
742
- [[FIRPhoneAuthProvider providerWithAuth:[FIRAuth authWithApp:firebaseApp]]
883
+ [FIRPhoneAuthProvider.provider
743
884
  verifyPhoneNumber:phoneNumber
744
885
  UIDelegate:nil
745
886
  completion:^(NSString *_Nullable verificationID, NSError *_Nullable error) {
@@ -1020,8 +1161,28 @@ RCT_EXPORT_METHOD(useEmulator
1020
1161
  }
1021
1162
  }
1022
1163
 
1164
+ - (NSDictionary *)multiFactorResolverToDict:(FIRMultiFactorResolver *)resolver {
1165
+ // Temporarily store the non-serializable session for later
1166
+ NSString *sessionHash = [NSString stringWithFormat:@"%@", @([resolver.session hash])];
1167
+
1168
+ return @{
1169
+ @"hints" : [self convertMultiFactorData:resolver.hints],
1170
+ @"session" : sessionHash,
1171
+ };
1172
+ }
1173
+
1174
+ - (NSString *)getJSFactorId:(NSString *)factorId {
1175
+ if ([factorId isEqualToString:@"1"]) {
1176
+ // Only phone is supported by the front-end so far
1177
+ return @"phone";
1178
+ }
1179
+
1180
+ return factorId;
1181
+ }
1182
+
1023
1183
  - (void)promiseRejectAuthException:(RCTPromiseRejectBlock)reject error:(NSError *)error {
1024
1184
  NSDictionary *jsError = [self getJSError:(error)];
1185
+
1025
1186
  [RNFBSharedUtils
1026
1187
  rejectPromiseWithUserInfo:reject
1027
1188
  userInfo:(NSMutableDictionary *)@{
@@ -1029,6 +1190,7 @@ RCT_EXPORT_METHOD(useEmulator
1029
1190
  @"message" : [jsError valueForKey:@"message"],
1030
1191
  @"nativeErrorMessage" : [jsError valueForKey:@"nativeErrorMessage"],
1031
1192
  @"authCredential" : [jsError valueForKey:@"authCredential"],
1193
+ @"resolver" : [jsError valueForKey:@"resolver"]
1032
1194
  }];
1033
1195
  }
1034
1196
 
@@ -1063,6 +1225,9 @@ RCT_EXPORT_METHOD(useEmulator
1063
1225
  message = @"This operation is sensitive and requires recent authentication. Log in again "
1064
1226
  @"before retrying this request.";
1065
1227
  break;
1228
+ case FIRAuthErrorCodeSecondFactorRequired:
1229
+ message = @"Please complete a second factor challenge to finish signing into this account.";
1230
+ break;
1066
1231
  case FIRAuthErrorCodeAccountExistsWithDifferentCredential:
1067
1232
  message = @"An account already exists with the same email address but different sign-in "
1068
1233
  @"credentials. Sign in using a provider associated with this email address.";
@@ -1098,6 +1263,12 @@ RCT_EXPORT_METHOD(useEmulator
1098
1263
  case FIRAuthErrorCodeInternalError:
1099
1264
  message = @"An internal error has occurred, please try again.";
1100
1265
  break;
1266
+ case FIRAuthErrorCodeInvalidPhoneNumber:
1267
+ message = @"The format of the phone number provided is incorrect. "
1268
+ @"Please enter the phone number in a format that can be parsed into E.164 format. "
1269
+ @"E.164 phone numbers are written in the format [+][country code][subscriber "
1270
+ @"number including area code].";
1271
+ break;
1101
1272
  default:
1102
1273
  break;
1103
1274
  }
@@ -1108,11 +1279,22 @@ RCT_EXPORT_METHOD(useEmulator
1108
1279
  authCredentialDict = [self authCredentialToDict:authCredential];
1109
1280
  }
1110
1281
 
1282
+ NSDictionary *resolverDict = nil;
1283
+ if ([error userInfo][FIRAuthErrorUserInfoMultiFactorResolverKey] != nil) {
1284
+ FIRMultiFactorResolver *resolver =
1285
+ (FIRMultiFactorResolver *)error.userInfo[FIRAuthErrorUserInfoMultiFactorResolverKey];
1286
+ resolverDict = [self multiFactorResolverToDict:resolver];
1287
+
1288
+ NSString *sessionKey = [NSString stringWithFormat:@"%@", @([resolver.session hash])];
1289
+ cachedResolver[sessionKey] = resolver;
1290
+ }
1291
+
1111
1292
  return @{
1112
1293
  @"code" : code,
1113
1294
  @"message" : message,
1114
1295
  @"nativeErrorMessage" : nativeErrorMessage,
1115
1296
  @"authCredential" : authCredentialDict != nil ? (id)authCredentialDict : [NSNull null],
1297
+ @"resolver" : resolverDict != nil ? (id)resolverDict : [NSNull null]
1116
1298
  };
1117
1299
  }
1118
1300
 
@@ -1251,10 +1433,28 @@ RCT_EXPORT_METHOD(useEmulator
1251
1433
  keyProviderId : [user.providerID lowercaseString],
1252
1434
  @"refreshToken" : user.refreshToken,
1253
1435
  @"tenantId" : user.tenantID ? (id)user.tenantID : [NSNull null],
1254
- keyUid : user.uid
1436
+ keyUid : user.uid,
1437
+ @"multiFactor" :
1438
+ @{@"enrolledFactors" : [self convertMultiFactorData:user.multiFactor.enrolledFactors]}
1255
1439
  };
1256
1440
  }
1257
1441
 
1442
+ - (NSArray<NSMutableDictionary *> *)convertMultiFactorData:(NSArray<FIRMultiFactorInfo *> *)hints {
1443
+ NSMutableArray *enrolledFactors = [NSMutableArray array];
1444
+
1445
+ for (FIRPhoneMultiFactorInfo *hint in hints) {
1446
+ NSString *enrollmentDate =
1447
+ [[[NSISO8601DateFormatter alloc] init] stringFromDate:hint.enrollmentDate];
1448
+ [enrolledFactors addObject:@{
1449
+ @"uid" : hint.UID,
1450
+ @"factorId" : [self getJSFactorId:(hint.factorID)],
1451
+ @"displayName" : hint.displayName == nil ? [NSNull null] : hint.displayName,
1452
+ @"enrollmentDate" : enrollmentDate,
1453
+ }];
1454
+ }
1455
+ return enrolledFactors;
1456
+ }
1457
+
1258
1458
  - (NSDictionary *)authCredentialToDict:(FIRAuthCredential *)authCredential {
1259
1459
  NSString *authCredentialHash = [NSString stringWithFormat:@"%@", @([authCredential hash])];
1260
1460
 
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Base class to facilitate multi-factor authentication.
3
+ */
4
+ export default class MultiFactorResolver {
5
+ constructor(auth, resolver) {
6
+ this._auth = auth;
7
+ this.hints = resolver.hints;
8
+ this.session = resolver.session;
9
+ }
10
+
11
+ resolveSignIn(assertion) {
12
+ const { token, secret } = assertion;
13
+ return this._auth.resolveMultiFactorSignIn(this.session, token, secret);
14
+ }
15
+ }
@@ -0,0 +1,33 @@
1
+ /*
2
+ * Copyright (c) 2016-present Invertase Limited & Contributors
3
+ *
4
+ * Licensed under the Apache License, Version 2.0 (the "License");
5
+ * you may not use this library except in compliance with the License.
6
+ * You may obtain a copy of the License at
7
+ *
8
+ * http://www.apache.org/licenses/LICENSE-2.0
9
+ *
10
+ * Unless required by applicable law or agreed to in writing, software
11
+ * distributed under the License is distributed on an "AS IS" BASIS,
12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ * See the License for the specific language governing permissions and
14
+ * limitations under the License.
15
+ *
16
+ */
17
+
18
+ export default class PhoneMultiFactorGenerator {
19
+ static FACTOR_ID = 'phone';
20
+
21
+ constructor() {
22
+ throw new Error(
23
+ '`new PhoneMultiFactorGenerator()` is not supported on the native Firebase SDKs.',
24
+ );
25
+ }
26
+
27
+ static assertion(credential) {
28
+ // There is no logic here, we mainly do this for API compatibility
29
+ // (following the Web API).
30
+ const { token, secret } = credential;
31
+ return { token, secret };
32
+ }
33
+ }
package/lib/User.js CHANGED
@@ -48,6 +48,10 @@ export default class User {
48
48
  };
49
49
  }
50
50
 
51
+ get multiFactor() {
52
+ return this._user.multiFactor || null;
53
+ }
54
+
51
55
  get phoneNumber() {
52
56
  return this._user.phoneNumber || null;
53
57
  }
@@ -0,0 +1,19 @@
1
+ import MultiFactorResolver from './MultiFactorResolver.js';
2
+
3
+ /**
4
+ * Create a new resolver based on an auth instance and error
5
+ * object.
6
+ *
7
+ * Returns null if no resolver object can be found on the error.
8
+ */
9
+ export function getMultiFactorResolver(auth, error) {
10
+ if (
11
+ error.hasOwnProperty('userInfo') &&
12
+ error.userInfo.hasOwnProperty('resolver') &&
13
+ error.userInfo.resolver
14
+ ) {
15
+ return new MultiFactorResolver(auth, error.userInfo.resolver);
16
+ }
17
+
18
+ return null;
19
+ }
package/lib/index.d.ts CHANGED
@@ -59,6 +59,11 @@ export namespace FirebaseAuthTypes {
59
59
  * you might receive an updated credential (depending of provider) which you can use to recover from the error.
60
60
  */
61
61
  authCredential: AuthCredential | null;
62
+ /**
63
+ * When trying to sign in the user might be prompted for a second factor confirmation. Can
64
+ * can use this object to initialize the second factor flow and recover from the error.
65
+ */
66
+ resolver: MultiFactorResolver | null;
62
67
  };
63
68
  }
64
69
 
@@ -189,10 +194,23 @@ export namespace FirebaseAuthTypes {
189
194
  ERROR: 'error';
190
195
  }
191
196
 
197
+ export interface PhoneMultiFactorGenerator {
198
+ /**
199
+ * Identifies second factors of type phone.
200
+ */
201
+ FACTOR_ID: FactorId.PHONE;
202
+
203
+ /**
204
+ * Build a MultiFactorAssertion to resolve the multi-factor sign in process.
205
+ */
206
+ assertion(credential: AuthCredential): MultiFactorAssertion;
207
+ }
208
+
192
209
  /**
193
210
  * firebase.auth.X
194
211
  */
195
212
  export interface Statics {
213
+ getMultiFactorResolver: getMultiFactorResolver;
196
214
  /**
197
215
  * Email and password auth provider implementation.
198
216
  *
@@ -285,6 +303,11 @@ export namespace FirebaseAuthTypes {
285
303
  * ```
286
304
  */
287
305
  PhoneAuthState: PhoneAuthState;
306
+
307
+ /**
308
+ * A PhoneMultiFactorGenerator interface.
309
+ */
310
+ PhoneMultiFactorGenerator: PhoneMultiFactorGenerator;
288
311
  }
289
312
 
290
313
  /**
@@ -360,6 +383,129 @@ export namespace FirebaseAuthTypes {
360
383
  lastSignInTime?: string;
361
384
  }
362
385
 
386
+ /**
387
+ * Identifies the type of a second factor.
388
+ */
389
+ export enum FactorId {
390
+ PHONE = 'phone',
391
+ }
392
+
393
+ /**
394
+ * Contains information about a second factor.
395
+ */
396
+ export interface MultiFactorInfo {
397
+ /**
398
+ * User friendly name for this factor.
399
+ */
400
+ displayName?: string;
401
+ /**
402
+ * Time the second factor was enrolled, in UTC.
403
+ */
404
+ enrollmentTime: string;
405
+ /**
406
+ * Type of factor.
407
+ */
408
+ factorId: FactorId;
409
+ /**
410
+ * Unique id for this factor.
411
+ */
412
+ uid: string;
413
+ }
414
+
415
+ export interface MultiFactorAssertion {
416
+ token: string;
417
+ secret: string;
418
+ }
419
+
420
+ /**
421
+ * Facilitates the recovery when a user needs to provide a second factor to sign-in.
422
+ */
423
+ export interface MultiFactorResolver {
424
+ /**
425
+ * A list of enrolled factors that can be used to complete the multi-factor challenge.
426
+ */
427
+ hints: MultiFactorInfo[];
428
+ /**
429
+ * Serialized session this resolver belongs to.
430
+ */
431
+ session: string;
432
+
433
+ /**
434
+ * For testing purposes only
435
+ */
436
+ _auth: FirebaseAuthTypes.Module;
437
+
438
+ /**
439
+ * Resolve the multi factor flow.
440
+ */
441
+ resolveSignIn(assertion: MultiFactorAssertion): Promise<UserCredential>;
442
+ }
443
+
444
+ /**
445
+ * Try and obtain a #{@link MultiFactorResolver} instance based on an error.
446
+ * Returns null if no resolver object could be found.
447
+ *
448
+ * #### Example
449
+ *
450
+ * ```js
451
+ * const auth = firebase.auth();
452
+ * auth.signInWithEmailAndPassword(email, password).then((user) => {
453
+ * // signed in
454
+ * }).catch((error) => {
455
+ * if (error.code === 'auth/multi-factor-auth-required') {
456
+ * const resolver = getMultiFactorResolver(auth, error);
457
+ * }
458
+ * });
459
+ * ```
460
+ */
461
+ export type getMultiFactorResolver = (
462
+ auth: FirebaseAuthTypes.Module,
463
+ error: unknown,
464
+ ) => MultiFactorResolver | null;
465
+
466
+ /**
467
+ * The entry point for most multi-factor operations.
468
+ */
469
+ export interface MultiFactorUser {
470
+ /**
471
+ * Returns the user's enrolled factors.
472
+ */
473
+ enrolledFactors: MultiFactorInfo[];
474
+
475
+ /**
476
+ * Return the session id for this user.
477
+ */
478
+ getSession(): Promise<string>;
479
+
480
+ /**
481
+ * Enroll an additional factor. Provide an optional display name that can be shown to the user.
482
+ * The method will ensure the user state is reloaded after successfully enrolling a factor.
483
+ */
484
+ enroll(assertion: MultiFactorAssertion, displayName?: string): Promise<void>;
485
+ }
486
+
487
+ /**
488
+ * Return the #{@link MultiFactorUser} instance for the current user.
489
+ */
490
+ export type multiFactor = (auth: FirebaseAuthTypes.Module) => Promise<MultiFactorUser>;
491
+
492
+ /**
493
+ * Holds information about the user's enrolled factors.
494
+ *
495
+ * #### Example
496
+ *
497
+ * ```js
498
+ * const user = firebase.auth().currentUser;
499
+ * console.log('User multi factors: ', user.multiFactor);
500
+ * ```
501
+ */
502
+ export interface MultiFactor {
503
+ /**
504
+ * Returns the enrolled factors
505
+ */
506
+ enrolledFactors: MultiFactorInfo[];
507
+ }
508
+
363
509
  /**
364
510
  * Represents a collection of standard profile information for a user. Can be used to expose
365
511
  * profile information returned by an identity provider, such as Google Sign-In or Facebook Login.
@@ -904,6 +1050,11 @@ export namespace FirebaseAuthTypes {
904
1050
  */
905
1051
  metadata: UserMetadata;
906
1052
 
1053
+ /**
1054
+ * Returns the {@link auth.MultiFactor} associated with this user.
1055
+ */
1056
+ multiFactor: MultiFactor | null;
1057
+
907
1058
  /**
908
1059
  * Returns the phone number of the user, as stored in the Firebase project's user database,
909
1060
  * or null if none exists. This can be updated at any time by calling {@link auth.User#updatePhoneNumber}.
@@ -1412,6 +1563,11 @@ export namespace FirebaseAuthTypes {
1412
1563
  forceResend?: boolean,
1413
1564
  ): PhoneAuthListener;
1414
1565
 
1566
+ /**
1567
+ * Obtain a verification id to complete the multi-factor sign-in flow.
1568
+ */
1569
+ verifyPhoneNumberWithMultiFactorInfo(hint: MultiFactorInfo, session: string): Promise<string>;
1570
+
1415
1571
  /**
1416
1572
  * Creates a new user with an email and password.
1417
1573
  *
package/lib/index.js CHANGED
@@ -35,11 +35,14 @@ import GithubAuthProvider from './providers/GithubAuthProvider';
35
35
  import GoogleAuthProvider from './providers/GoogleAuthProvider';
36
36
  import OAuthProvider from './providers/OAuthProvider';
37
37
  import PhoneAuthProvider from './providers/PhoneAuthProvider';
38
+ import PhoneMultiFactorGenerator from './PhoneMultiFactorGenerator';
38
39
  import TwitterAuthProvider from './providers/TwitterAuthProvider';
39
40
  import AppleAuthProvider from './providers/AppleAuthProvider';
40
41
  import Settings from './Settings';
41
42
  import User from './User';
42
43
  import version from './version';
44
+ import { getMultiFactorResolver } from './getMultiFactorResolver';
45
+ import { multiFactor } from './multiFactor';
43
46
 
44
47
  const statics = {
45
48
  AppleAuthProvider,
@@ -49,6 +52,7 @@ const statics = {
49
52
  GithubAuthProvider,
50
53
  TwitterAuthProvider,
51
54
  FacebookAuthProvider,
55
+ PhoneMultiFactorGenerator,
52
56
  OAuthProvider,
53
57
  PhoneAuthState: {
54
58
  CODE_SENT: 'sent',
@@ -56,6 +60,8 @@ const statics = {
56
60
  AUTO_VERIFIED: 'verified',
57
61
  ERROR: 'error',
58
62
  },
63
+ getMultiFactorResolver,
64
+ multiFactor,
59
65
  };
60
66
 
61
67
  const namespace = 'auth';
@@ -250,6 +256,23 @@ class FirebaseAuthModule extends FirebaseModule {
250
256
  return new PhoneAuthListener(this, phoneNumber, _autoVerifyTimeout, _forceResend);
251
257
  }
252
258
 
259
+ verifyPhoneNumberWithMultiFactorInfo(multiFactorHint, session) {
260
+ return this.native.verifyPhoneNumberWithMultiFactorInfo(multiFactorHint.uid, session);
261
+ }
262
+
263
+ verifyPhoneNumberForMultiFactor(phoneInfoOptions) {
264
+ const { phoneNumber, session } = phoneInfoOptions;
265
+ return this.native.verifyPhoneNumberForMultiFactor(phoneNumber, session);
266
+ }
267
+
268
+ resolveMultiFactorSignIn(session, verificationId, verificationCode) {
269
+ return this.native
270
+ .resolveMultiFactorSignIn(session, verificationId, verificationCode)
271
+ .then(userCredential => {
272
+ return this._setUserCredential(userCredential);
273
+ });
274
+ }
275
+
253
276
  createUserWithEmailAndPassword(email, password) {
254
277
  return this.native
255
278
  .createUserWithEmailAndPassword(email, password)
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Return a MultiFactorUser instance the gateway to multi-factor operations.
3
+ */
4
+ export function multiFactor(auth) {
5
+ return new MultiFactorUser(auth);
6
+ }
7
+
8
+ export class MultiFactorUser {
9
+ constructor(auth) {
10
+ this._auth = auth;
11
+ this._user = auth.currentUser;
12
+ this.enrolledFactor = auth.currentUser.multiFactor.enrolledFactors;
13
+ }
14
+
15
+ getSession() {
16
+ return this._auth.native.getSession();
17
+ }
18
+
19
+ /**
20
+ * Finalize the enrollment process for the given multi-factor assertion.
21
+ * Optionally set a displayName. This method will reload the current user
22
+ * profile, which is necessary to see the multi-factor changes.
23
+ */
24
+ async enroll(multiFactorAssertion, displayName) {
25
+ const { token, secret } = multiFactorAssertion;
26
+ await this._auth.native.finalizeMultiFactorEnrollment(token, secret, displayName);
27
+
28
+ // We need to reload the user otherwise the changes are not visible
29
+ return this._auth.currentUser.reload();
30
+ }
31
+
32
+ unenroll() {
33
+ return Promise.reject(new Error('No implemented yet.'));
34
+ }
35
+ }
package/lib/version.js CHANGED
@@ -1,2 +1,2 @@
1
1
  // Generated by genversion.
2
- module.exports = '16.2.0';
2
+ module.exports = '16.3.1';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@react-native-firebase/auth",
3
- "version": "16.2.0",
3
+ "version": "16.3.1",
4
4
  "author": "Invertase <oss@invertase.io> (http://invertase.io)",
5
5
  "description": "React Native Firebase - The authentication module provides an easy-to-use API to integrate an authentication workflow into new and existing applications. React Native Firebase provides access to all Firebase authentication methods and identity providers.",
6
6
  "main": "lib/index.js",
@@ -22,10 +22,10 @@
22
22
  "auth"
23
23
  ],
24
24
  "peerDependencies": {
25
- "@react-native-firebase/app": "16.2.0"
25
+ "@react-native-firebase/app": "16.3.1"
26
26
  },
27
27
  "publishConfig": {
28
28
  "access": "public"
29
29
  },
30
- "gitHead": "e6fbf592e1014508528b302e4aa4cc63f052ddd1"
30
+ "gitHead": "1f2385bfb90ee5cad2aab23332d1d13fdf0d81c2"
31
31
  }