@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 +10 -0
- package/__tests__/auth.test.ts +49 -0
- package/android/src/main/java/io/invertase/firebase/auth/ReactNativeFirebaseAuthModule.java +320 -1
- package/ios/RNFBAuth/RNFBAuthModule.h +2 -1
- package/ios/RNFBAuth/RNFBAuthModule.m +202 -2
- package/lib/MultiFactorResolver.js +15 -0
- package/lib/PhoneMultiFactorGenerator.js +33 -0
- package/lib/User.js +4 -0
- package/lib/getMultiFactorResolver.js +19 -0
- package/lib/index.d.ts +156 -0
- package/lib/index.js +23 -0
- package/lib/multiFactor.js +35 -0
- package/lib/version.js +1 -1
- package/package.json +3 -3
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
|
package/__tests__/auth.test.ts
CHANGED
@@ -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
|
-
|
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
|
-
[
|
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
@@ -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
|
+
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.
|
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.
|
25
|
+
"@react-native-firebase/app": "16.3.1"
|
26
26
|
},
|
27
27
|
"publishConfig": {
|
28
28
|
"access": "public"
|
29
29
|
},
|
30
|
-
"gitHead": "
|
30
|
+
"gitHead": "1f2385bfb90ee5cad2aab23332d1d13fdf0d81c2"
|
31
31
|
}
|