@react-native-firebase/auth 23.1.2 → 23.2.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,23 @@
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
+ ## [23.2.1](https://github.com/invertase/react-native-firebase/compare/v23.2.0...v23.2.1) (2025-09-01)
7
+
8
+ **Note:** Version bump only for package @react-native-firebase/auth
9
+
10
+ ## [23.2.0](https://github.com/invertase/react-native-firebase/compare/v23.1.2...v23.2.0) (2025-08-29)
11
+
12
+ ### Features
13
+
14
+ - **auth:** QR code generation from TOTP secrets ([dd8e14b](https://github.com/invertase/react-native-firebase/commit/dd8e14b3387a8d6a0a48e3379d6379d7a3dae14d))
15
+ - **auth:** support native TOTP app open ([213ee45](https://github.com/invertase/react-native-firebase/commit/213ee456894d21666ccdd6f179232aefba19658a))
16
+ - **auth:** support TOTP enroll and unenroll ([0ad0658](https://github.com/invertase/react-native-firebase/commit/0ad0658ecb6d586c4428bf34b034add03fb90e3e))
17
+ - **other:** implement TOTP auth for Other platform ([3fbc43a](https://github.com/invertase/react-native-firebase/commit/3fbc43a1f1ccf768c5f76a962a59d1850f73ba5a))
18
+
19
+ ### Bug Fixes
20
+
21
+ - **auth:** add TotpSecret type definition ([be01081](https://github.com/invertase/react-native-firebase/commit/be0108134c93c57fcdac1069ac28b7aab9fc62be))
22
+
6
23
  ## [23.1.2](https://github.com/invertase/react-native-firebase/compare/v23.1.1...v23.1.2) (2025-08-25)
7
24
 
8
25
  **Note:** Version bump only for package @react-native-firebase/auth
@@ -66,6 +66,9 @@ import com.google.firebase.auth.PhoneAuthProvider;
66
66
  import com.google.firebase.auth.PhoneMultiFactorAssertion;
67
67
  import com.google.firebase.auth.PhoneMultiFactorGenerator;
68
68
  import com.google.firebase.auth.PhoneMultiFactorInfo;
69
+ import com.google.firebase.auth.TotpMultiFactorAssertion;
70
+ import com.google.firebase.auth.TotpMultiFactorGenerator;
71
+ import com.google.firebase.auth.TotpSecret;
69
72
  import com.google.firebase.auth.TwitterAuthProvider;
70
73
  import com.google.firebase.auth.UserInfo;
71
74
  import com.google.firebase.auth.UserProfileChangeRequest;
@@ -107,6 +110,7 @@ class ReactNativeFirebaseAuthModule extends ReactNativeFirebaseModule {
107
110
 
108
111
  private final HashMap<String, MultiFactorResolver> mCachedResolvers = new HashMap<>();
109
112
  private final HashMap<String, MultiFactorSession> mMultiFactorSessions = new HashMap<>();
113
+ private final HashMap<String, TotpSecret> mTotpSecrets = new HashMap<>();
110
114
 
111
115
  // storage for anonymous phone auth credentials, used for linkWithCredentials
112
116
  // https://github.com/invertase/react-native-firebase/issues/4911
@@ -154,6 +158,7 @@ class ReactNativeFirebaseAuthModule extends ReactNativeFirebaseModule {
154
158
 
155
159
  mCachedResolvers.clear();
156
160
  mMultiFactorSessions.clear();
161
+ mTotpSecrets.clear();
157
162
  }
158
163
 
159
164
  @ReactMethod
@@ -1130,6 +1135,26 @@ class ReactNativeFirebaseAuthModule extends ReactNativeFirebaseModule {
1130
1135
  });
1131
1136
  }
1132
1137
 
1138
+ @ReactMethod
1139
+ public void unenrollMultiFactor(
1140
+ final String appName, final String factorUID, final Promise promise) {
1141
+ FirebaseApp firebaseApp = FirebaseApp.getInstance(appName);
1142
+ FirebaseAuth firebaseAuth = FirebaseAuth.getInstance(firebaseApp);
1143
+ firebaseAuth
1144
+ .getCurrentUser()
1145
+ .getMultiFactor()
1146
+ .unenroll(factorUID)
1147
+ .addOnCompleteListener(
1148
+ task -> {
1149
+ if (!task.isSuccessful()) {
1150
+ rejectPromiseWithExceptionMap(promise, task.getException());
1151
+ return;
1152
+ }
1153
+
1154
+ promise.resolve(null);
1155
+ });
1156
+ }
1157
+
1133
1158
  @ReactMethod
1134
1159
  public void verifyPhoneNumberWithMultiFactorInfo(
1135
1160
  final String appName, final String hintUid, final String sessionKey, final Promise promise) {
@@ -1280,6 +1305,67 @@ class ReactNativeFirebaseAuthModule extends ReactNativeFirebaseModule {
1280
1305
  });
1281
1306
  }
1282
1307
 
1308
+ @ReactMethod
1309
+ public void generateQrCodeUrl(
1310
+ final String appName,
1311
+ final String secretKey,
1312
+ final String account,
1313
+ final String issuer,
1314
+ final Promise promise) {
1315
+
1316
+ TotpSecret secret = mTotpSecrets.get(secretKey);
1317
+ if (secret == null) {
1318
+ rejectPromiseWithCodeAndMessage(
1319
+ promise, "invalid-multi-factor-secret", "can't find secret for provided key");
1320
+ return;
1321
+ }
1322
+ promise.resolve(secret.generateQrCodeUrl(account, issuer));
1323
+ }
1324
+
1325
+ @ReactMethod
1326
+ public void openInOtpApp(final String appName, final String secretKey, final String qrCodeUri) {
1327
+ TotpSecret secret = mTotpSecrets.get(secretKey);
1328
+ if (secret != null) {
1329
+ secret.openInOtpApp(qrCodeUri);
1330
+ }
1331
+ }
1332
+
1333
+ @ReactMethod
1334
+ public void finalizeTotpEnrollment(
1335
+ final String appName,
1336
+ final String totpSecret,
1337
+ final String verificationCode,
1338
+ @Nullable final String displayName,
1339
+ final Promise promise) {
1340
+
1341
+ TotpSecret secret = mTotpSecrets.get(totpSecret);
1342
+ if (secret == null) {
1343
+ rejectPromiseWithCodeAndMessage(
1344
+ promise, "invalid-multi-factor-secret", "can't find secret for provided key");
1345
+ return;
1346
+ }
1347
+
1348
+ TotpMultiFactorAssertion assertion =
1349
+ TotpMultiFactorGenerator.getAssertionForEnrollment(secret, verificationCode);
1350
+
1351
+ FirebaseApp firebaseApp = FirebaseApp.getInstance(appName);
1352
+ FirebaseAuth firebaseAuth = FirebaseAuth.getInstance(firebaseApp);
1353
+
1354
+ firebaseAuth
1355
+ .getCurrentUser()
1356
+ .getMultiFactor()
1357
+ .enroll(assertion, displayName)
1358
+ .addOnCompleteListener(
1359
+ task -> {
1360
+ if (!task.isSuccessful()) {
1361
+ rejectPromiseWithExceptionMap(promise, task.getException());
1362
+ return;
1363
+ }
1364
+
1365
+ promise.resolve(null);
1366
+ });
1367
+ }
1368
+
1283
1369
  /**
1284
1370
  * This method is intended to resolve a {@link PhoneAuthCredential} obtained through a
1285
1371
  * multi-factor authentication flow. A credential can either be obtained using:
@@ -1335,6 +1421,70 @@ class ReactNativeFirebaseAuthModule extends ReactNativeFirebaseModule {
1335
1421
  resolveMultiFactorCredential(credential, session, promise);
1336
1422
  }
1337
1423
 
1424
+ @ReactMethod
1425
+ public void resolveTotpSignIn(
1426
+ final String appName,
1427
+ final String sessionKey,
1428
+ final String uid,
1429
+ final String oneTimePassword,
1430
+ final Promise promise) {
1431
+
1432
+ final MultiFactorAssertion assertion =
1433
+ TotpMultiFactorGenerator.getAssertionForSignIn(uid, oneTimePassword);
1434
+
1435
+ final MultiFactorResolver resolver = mCachedResolvers.get(sessionKey);
1436
+ if (resolver == null) {
1437
+ // See https://firebase.google.com/docs/reference/node/firebase.auth.multifactorresolver for
1438
+ // the error code
1439
+ rejectPromiseWithCodeAndMessage(
1440
+ promise,
1441
+ "invalid-multi-factor-session",
1442
+ "No resolver for session found. Is the session id correct?");
1443
+ return;
1444
+ }
1445
+
1446
+ resolver
1447
+ .resolveSignIn(assertion)
1448
+ .addOnCompleteListener(
1449
+ task -> {
1450
+ if (task.isSuccessful()) {
1451
+ AuthResult authResult = task.getResult();
1452
+ promiseWithAuthResult(authResult, promise);
1453
+ } else {
1454
+ promiseRejectAuthException(promise, task.getException());
1455
+ }
1456
+ });
1457
+ }
1458
+
1459
+ @ReactMethod
1460
+ public void generateTotpSecret(
1461
+ final String appName, final String sessionKey, final Promise promise) {
1462
+
1463
+ final MultiFactorSession session = mMultiFactorSessions.get(sessionKey);
1464
+ if (session == null) {
1465
+ rejectPromiseWithCodeAndMessage(
1466
+ promise,
1467
+ "invalid-multi-factor-session",
1468
+ "No resolver for session found. Is the session id correct?");
1469
+ return;
1470
+ }
1471
+
1472
+ TotpMultiFactorGenerator.generateSecret(session)
1473
+ .addOnCompleteListener(
1474
+ task -> {
1475
+ if (task.isSuccessful()) {
1476
+ TotpSecret totpSecret = task.getResult();
1477
+ String totpSecretKey = totpSecret.getSharedSecretKey();
1478
+ mTotpSecrets.put(totpSecretKey, totpSecret);
1479
+ WritableMap result = Arguments.createMap();
1480
+ result.putString("secretKey", totpSecretKey);
1481
+ promise.resolve(result);
1482
+ } else {
1483
+ promiseRejectAuthException(promise, task.getException());
1484
+ }
1485
+ });
1486
+ }
1487
+
1338
1488
  @ReactMethod
1339
1489
  public void confirmationResultConfirm(
1340
1490
  String appName, final String verificationCode, final Promise promise) {
@@ -58,6 +58,7 @@ static __strong NSMutableDictionary<NSString *, FIRAuthCredential *> *credential
58
58
  #if TARGET_OS_IOS
59
59
  static __strong NSMutableDictionary<NSString *, FIRMultiFactorResolver *> *cachedResolver;
60
60
  static __strong NSMutableDictionary<NSString *, FIRMultiFactorSession *> *cachedSessions;
61
+ static __strong NSMutableDictionary<NSString *, FIRTOTPSecret *> *cachedTotpSecrets;
61
62
  #endif
62
63
 
63
64
  @implementation RNFBAuthModule
@@ -81,6 +82,7 @@ RCT_EXPORT_MODULE();
81
82
  #if TARGET_OS_IOS
82
83
  cachedResolver = [[NSMutableDictionary alloc] init];
83
84
  cachedSessions = [[NSMutableDictionary alloc] init];
85
+ cachedTotpSecrets = [[NSMutableDictionary alloc] init];
84
86
  #endif
85
87
  });
86
88
  return self;
@@ -110,6 +112,7 @@ RCT_EXPORT_MODULE();
110
112
  #if TARGET_OS_IOS
111
113
  [cachedResolver removeAllObjects];
112
114
  [cachedSessions removeAllObjects];
115
+ [cachedTotpSecrets removeAllObjects];
113
116
  #endif
114
117
  }
115
118
 
@@ -967,6 +970,87 @@ RCT_EXPORT_METHOD(resolveMultiFactorSignIn
967
970
  }];
968
971
  }
969
972
 
973
+ RCT_EXPORT_METHOD(resolveTotpSignIn
974
+ : (FIRApp *)firebaseApp
975
+ : (NSString *)sessionKey
976
+ : (NSString *)uid
977
+ : (NSString *)oneTimePassword
978
+ : (RCTPromiseResolveBlock)resolve
979
+ : (RCTPromiseRejectBlock)reject) {
980
+ DLog(@"using instance resolve TotpSignIn: %@", firebaseApp.name);
981
+
982
+ FIRMultiFactorAssertion *assertion =
983
+ [FIRTOTPMultiFactorGenerator assertionForSignInWithEnrollmentID:uid
984
+ oneTimePassword:oneTimePassword];
985
+ [cachedResolver[sessionKey] resolveSignInWithAssertion:assertion
986
+ completion:^(FIRAuthDataResult *_Nullable authResult,
987
+ NSError *_Nullable error) {
988
+ DLog(@"authError: %@", error) if (error) {
989
+ [self promiseRejectAuthException:reject
990
+ error:error];
991
+ }
992
+ else {
993
+ [self promiseWithAuthResult:resolve
994
+ rejecter:reject
995
+ authResult:authResult];
996
+ }
997
+ }];
998
+ }
999
+
1000
+ RCT_EXPORT_METHOD(generateTotpSecret
1001
+ : (FIRApp *)firebaseApp
1002
+ : (NSString *)sessionKey
1003
+ : (RCTPromiseResolveBlock)resolve
1004
+ : (RCTPromiseRejectBlock)reject) {
1005
+ DLog(@"using instance resolve generateTotpSecret: %@", firebaseApp.name);
1006
+
1007
+ FIRMultiFactorSession *session = cachedSessions[sessionKey];
1008
+ DLog(@"using sessionKey: %@", sessionKey);
1009
+ DLog(@"using session: %@", session);
1010
+ [FIRTOTPMultiFactorGenerator
1011
+ generateSecretWithMultiFactorSession:session
1012
+ completion:^(FIRTOTPSecret *_Nullable totpSecret,
1013
+ NSError *_Nullable error) {
1014
+ DLog(@"authError: %@", error) if (error) {
1015
+ [self promiseRejectAuthException:reject error:error];
1016
+ }
1017
+ else {
1018
+ NSString *secretKey = totpSecret.sharedSecretKey;
1019
+ DLog(@"secretKey generated: %@", secretKey);
1020
+ cachedTotpSecrets[secretKey] = totpSecret;
1021
+ DLog(@"cachedSecret: %@", cachedTotpSecrets[secretKey]);
1022
+ resolve(@{
1023
+ @"secretKey" : secretKey,
1024
+ });
1025
+ }
1026
+ }];
1027
+ }
1028
+
1029
+ RCT_EXPORT_METHOD(generateQrCodeUrl
1030
+ : (FIRApp *)firebaseApp
1031
+ : (NSString *)secretKey
1032
+ : (NSString *)accountName
1033
+ : (NSString *)issuer
1034
+ : (RCTPromiseResolveBlock)resolve
1035
+ : (RCTPromiseRejectBlock)reject) {
1036
+ DLog(@"generateQrCodeUrl using instance resolve generateQrCodeUrl: %@", firebaseApp.name);
1037
+ DLog(@"generateQrCodeUrl using secretKey: %@", secretKey);
1038
+ FIRTOTPSecret *totpSecret = cachedTotpSecrets[secretKey];
1039
+ NSString *url = [totpSecret generateQRCodeURLWithAccountName:accountName issuer:issuer];
1040
+ DLog(@"generateQrCodeUrl got QR Code URL %@", url);
1041
+ resolve(url);
1042
+ }
1043
+
1044
+ RCT_EXPORT_METHOD(openInOtpApp
1045
+ : (FIRApp *)firebaseApp
1046
+ : (NSString *)secretKey
1047
+ : (NSString *)qrCodeUri) {
1048
+ DLog(@"generateQrCodeUrl using secretKey: %@", secretKey);
1049
+ FIRTOTPSecret *totpSecret = cachedTotpSecrets[secretKey];
1050
+ DLog(@"openInOtpApp using qrCodeUri: %@", qrCodeUri);
1051
+ [totpSecret openInOTPAppWithQRCodeURL:qrCodeUri];
1052
+ }
1053
+
970
1054
  RCT_EXPORT_METHOD(getSession
971
1055
  : (FIRApp *)firebaseApp
972
1056
  : (RCTPromiseResolveBlock)resolve
@@ -985,6 +1069,26 @@ RCT_EXPORT_METHOD(getSession
985
1069
  }];
986
1070
  }
987
1071
 
1072
+ RCT_EXPORT_METHOD(unenrollMultiFactor
1073
+ : (FIRApp *)firebaseApp
1074
+ : (NSString *)factorUID
1075
+ : (RCTPromiseResolveBlock)resolve
1076
+ : (RCTPromiseRejectBlock)reject) {
1077
+ DLog(@"using instance unenrollMultiFactor: %@", firebaseApp.name);
1078
+
1079
+ FIRUser *user = [FIRAuth authWithApp:firebaseApp].currentUser;
1080
+ [user.multiFactor unenrollWithFactorUID:factorUID
1081
+ completion:^(NSError *_Nullable error) {
1082
+ if (error != nil) {
1083
+ [self promiseRejectAuthException:reject error:error];
1084
+ return;
1085
+ }
1086
+
1087
+ resolve(nil);
1088
+ return;
1089
+ }];
1090
+ }
1091
+
988
1092
  RCT_EXPORT_METHOD(finalizeMultiFactorEnrollment
989
1093
  : (FIRApp *)firebaseApp
990
1094
  : (NSString *)verificationId
@@ -1014,6 +1118,37 @@ RCT_EXPORT_METHOD(finalizeMultiFactorEnrollment
1014
1118
  }];
1015
1119
  }
1016
1120
 
1121
+ RCT_EXPORT_METHOD(finalizeTotpEnrollment
1122
+ : (FIRApp *)firebaseApp
1123
+ : (NSString *)totpSecret
1124
+ : (NSString *)verificationCode
1125
+ : (NSString *_Nullable)displayName
1126
+ : (RCTPromiseResolveBlock)resolve
1127
+ : (RCTPromiseRejectBlock)reject) {
1128
+ DLog(@"using instance finalizeTotpEnrollment: %@", firebaseApp.name);
1129
+
1130
+ FIRTOTPSecret *cachedTotpSecret = cachedTotpSecrets[totpSecret];
1131
+ DLog(@"using totpSecretKey: %@", totpSecret);
1132
+ DLog(@"using cachedSecret: %@", cachedTotpSecret);
1133
+ FIRTOTPMultiFactorAssertion *assertion =
1134
+ [FIRTOTPMultiFactorGenerator assertionForEnrollmentWithSecret:cachedTotpSecret
1135
+ oneTimePassword:verificationCode];
1136
+
1137
+ FIRUser *user = [FIRAuth authWithApp:firebaseApp].currentUser;
1138
+
1139
+ [user.multiFactor enrollWithAssertion:assertion
1140
+ displayName:displayName
1141
+ completion:^(NSError *_Nullable error) {
1142
+ if (error != nil) {
1143
+ [self promiseRejectAuthException:reject error:error];
1144
+ return;
1145
+ }
1146
+
1147
+ resolve(nil);
1148
+ return;
1149
+ }];
1150
+ }
1151
+
1017
1152
  RCT_EXPORT_METHOD(verifyPhoneNumber
1018
1153
  : (FIRApp *)firebaseApp
1019
1154
  : (NSString *)phoneNumber
@@ -1741,12 +1876,12 @@ RCT_EXPORT_METHOD(useEmulator
1741
1876
  @"enrollmentDate" : enrollmentTime,
1742
1877
  } mutableCopy];
1743
1878
 
1744
- // only support phone mutli factor
1745
1879
  if ([hint isKindOfClass:[FIRPhoneMultiFactorInfo class]]) {
1746
1880
  FIRPhoneMultiFactorInfo *phoneHint = (FIRPhoneMultiFactorInfo *)hint;
1747
1881
  factorDict[@"phoneNumber"] = phoneHint.phoneNumber;
1748
- [enrolledFactors addObject:factorDict];
1749
1882
  }
1883
+
1884
+ [enrolledFactors addObject:factorDict];
1750
1885
  }
1751
1886
  return enrolledFactors;
1752
1887
  }
@@ -9,7 +9,12 @@ export default class MultiFactorResolver {
9
9
  }
10
10
 
11
11
  resolveSignIn(assertion) {
12
- const { token, secret } = assertion;
13
- return this._auth.resolveMultiFactorSignIn(this.session, token, secret);
12
+ const { token, secret, uid, verificationCode } = assertion;
13
+
14
+ if (token && secret) {
15
+ return this._auth.resolveMultiFactorSignIn(this.session, token, secret);
16
+ }
17
+
18
+ return this._auth.resolveTotpSignIn(this.session, uid, verificationCode);
14
19
  }
15
20
  }
@@ -0,0 +1,56 @@
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
+ import { isOther } from '@react-native-firebase/app/lib/common';
19
+ import { TotpSecret } from './TotpSecret';
20
+ import { getAuth } from './modular';
21
+
22
+ export default class TotpMultiFactorGenerator {
23
+ static FACTOR_ID = 'totp';
24
+
25
+ constructor() {
26
+ throw new Error(
27
+ '`new TotpMultiFactorGenerator()` is not supported on the native Firebase SDKs.',
28
+ );
29
+ }
30
+
31
+ static assertionForSignIn(uid, verificationCode) {
32
+ if (isOther) {
33
+ // we require the web native assertion when using firebase-js-sdk
34
+ // as it has functions used by the SDK, a shim won't do
35
+ return getAuth().native.assertionForSignIn(uid, verificationCode);
36
+ }
37
+ return { uid, verificationCode };
38
+ }
39
+
40
+ static assertionForEnrollment(totpSecret, verificationCode) {
41
+ return { totpSecret: totpSecret.secretKey, verificationCode };
42
+ }
43
+
44
+ static async generateSecret(session, auth) {
45
+ if (!session) {
46
+ throw new Error('Session is required to generate a TOTP secret.');
47
+ }
48
+ const {
49
+ secretKey,
50
+ // Other properties are not publicly exposed in native APIs
51
+ // hashingAlgorithm, codeLength, codeIntervalSeconds, enrollmentCompletionDeadline
52
+ } = await auth.native.generateTotpSecret(session);
53
+
54
+ return new TotpSecret(secretKey, auth);
55
+ }
56
+ }
@@ -0,0 +1,67 @@
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
+ import { isString } from '@react-native-firebase/app/lib/common';
18
+
19
+ export class TotpSecret {
20
+ constructor(secretKey, auth) {
21
+ // The native TotpSecret has many more properties, but they are
22
+ // internal to the native SDKs, we only maintain the secret in JS layer
23
+ this.secretKey = secretKey;
24
+
25
+ // we do need a handle to the correct auth instance to generate QR codes etc
26
+ this.auth = auth;
27
+ }
28
+
29
+ /**
30
+ * Shared secret key/seed used for enrolling in TOTP MFA and generating OTPs.
31
+ */
32
+ secretKey = null;
33
+
34
+ /**
35
+ * Returns a QR code URL as described in
36
+ * https://github.com/google/google-authenticator/wiki/Key-Uri-Format
37
+ * This can be displayed to the user as a QR code to be scanned into a TOTP app like Google Authenticator.
38
+ * If the optional parameters are unspecified, an accountName of <userEmail> and issuer of <firebaseAppName> are used.
39
+ *
40
+ * @param accountName the name of the account/app along with a user identifier.
41
+ * @param issuer issuer of the TOTP (likely the app name).
42
+ * @returns A Promise that resolves to a QR code URL string.
43
+ */
44
+ async generateQrCodeUrl(accountName, issuer) {
45
+ // accountName and issure are nullable in the API specification but are
46
+ // required by tha native SDK. The JS SDK returns '' if they are missing/empty.
47
+ if (!isString(accountName) || !isString(issuer) || accountName === '' || issuer === '') {
48
+ return '';
49
+ }
50
+ return this.auth.native.generateQrCodeUrl(this.secretKey, accountName, issuer);
51
+ }
52
+
53
+ /**
54
+ * Opens the specified QR Code URL in an OTP authenticator app on the device.
55
+ * The shared secret key and account name will be populated in the OTP authenticator app.
56
+ * The URL uses the otpauth:// scheme and will be opened on an app that handles this scheme,
57
+ * if it exists on the device, possibly opening the ecocystem-specific app store with a generic
58
+ * query for compatible apps if no app exists on the device.
59
+ *
60
+ * @param qrCodeUrl the URL to open in the app, from generateQrCodeUrl
61
+ */
62
+ openInOtpApp(qrCodeUrl) {
63
+ if (isString(qrCodeUrl) && !qrCodeUrl !== '') {
64
+ return this.auth.native.openInOtpApp(this.secretKey, qrCodeUrl);
65
+ }
66
+ }
67
+ }
@@ -1,3 +1,4 @@
1
+ import { isOther } from '@react-native-firebase/app/lib/common';
1
2
  import MultiFactorResolver from './MultiFactorResolver.js';
2
3
 
3
4
  /**
@@ -7,6 +8,9 @@ import MultiFactorResolver from './MultiFactorResolver.js';
7
8
  * Returns null if no resolver object can be found on the error.
8
9
  */
9
10
  export function getMultiFactorResolver(auth, error) {
11
+ if (isOther) {
12
+ return auth.native.getMultiFactorResolver(error);
13
+ }
10
14
  if (
11
15
  error.hasOwnProperty('userInfo') &&
12
16
  error.userInfo.hasOwnProperty('resolver') &&
package/lib/index.d.ts CHANGED
@@ -275,6 +275,78 @@ export namespace FirebaseAuthTypes {
275
275
  assertion(credential: AuthCredential): MultiFactorAssertion;
276
276
  }
277
277
 
278
+ /**
279
+ * Represents a TOTP secret that is used for enrolling a TOTP second factor.
280
+ * Contains the shared secret key and other parameters to generate time-based
281
+ * one-time passwords. Implements methods to retrieve the shared secret key,
282
+ * generate a QR code URL, and open the QR code URL in an OTP authenticator app.
283
+ *
284
+ * Differs from standard firebase JS implementation in three ways:
285
+ * 1- there is no visibility into ony properties other than the secretKey
286
+ * 2- there is an added `openInOtpApp` method supported by native SDKs
287
+ * 3- the return value of generateQrCodeUrl is a Promise because react-native bridge is async
288
+ * @public
289
+ */
290
+ export declare class TotpSecret {
291
+ /** used internally to support non-default auth instances */
292
+ private readonly auth;
293
+ /**
294
+ * Shared secret key/seed used for enrolling in TOTP MFA and generating OTPs.
295
+ */
296
+ readonly secretKey: string;
297
+
298
+ private constructor();
299
+
300
+ /**
301
+ * Returns a QR code URL as described in
302
+ * https://github.com/google/google-authenticator/wiki/Key-Uri-Format
303
+ * This can be displayed to the user as a QR code to be scanned into a TOTP app like Google Authenticator.
304
+ * If the optional parameters are unspecified, an accountName of userEmail and issuer of firebaseAppName are used.
305
+ *
306
+ * @param accountName the name of the account/app along with a user identifier.
307
+ * @param issuer issuer of the TOTP (likely the app name).
308
+ * @returns A Promise that resolves to a QR code URL string.
309
+ */
310
+ async generateQrCodeUrl(accountName?: string, issuer?: string): Promise<string>;
311
+
312
+ /**
313
+ * Opens the specified QR Code URL in an OTP authenticator app on the device.
314
+ * The shared secret key and account name will be populated in the OTP authenticator app.
315
+ * The URL uses the otpauth:// scheme and will be opened on an app that handles this scheme,
316
+ * if it exists on the device, possibly opening the ecocystem-specific app store with a generic
317
+ * query for compatible apps if no app exists on the device.
318
+ *
319
+ * @param qrCodeUrl the URL to open in the app, from generateQrCodeUrl
320
+ */
321
+ openInOtpApp(qrCodeUrl: string): string;
322
+ }
323
+
324
+ export interface TotpMultiFactorGenerator {
325
+ FACTOR_ID: FactorId.TOTP;
326
+
327
+ assertionForSignIn(uid: string, totpSecret: string): MultiFactorAssertion;
328
+
329
+ assertionForEnrollment(secret: TotpSecret, code: string): MultiFactorAssertion;
330
+
331
+ /**
332
+ * @param auth - The Auth instance. Only used for native platforms, should be ignored on web.
333
+ */
334
+ generateSecret(
335
+ session: FirebaseAuthTypes.MultiFactorSession,
336
+ auth: FirebaseAuthTypes.Auth,
337
+ ): Promise<TotpSecret>;
338
+ }
339
+
340
+ export declare interface MultiFactorError extends AuthError {
341
+ /** Details about the MultiFactorError. */
342
+ readonly customData: AuthError['customData'] & {
343
+ /**
344
+ * The type of operation (sign-in, linking, or re-authentication) that raised the error.
345
+ */
346
+ readonly operationType: (typeof OperationType)[keyof typeof OperationType];
347
+ };
348
+ }
349
+
278
350
  /**
279
351
  * firebase.auth.X
280
352
  */
@@ -476,6 +548,7 @@ export namespace FirebaseAuthTypes {
476
548
  */
477
549
  export enum FactorId {
478
550
  PHONE = 'phone',
551
+ TOTP = 'totp',
479
552
  }
480
553
 
481
554
  /**
@@ -596,6 +669,12 @@ export namespace FirebaseAuthTypes {
596
669
  * The method will ensure the user state is reloaded after successfully enrolling a factor.
597
670
  */
598
671
  enroll(assertion: MultiFactorAssertion, displayName?: string): Promise<void>;
672
+
673
+ /**
674
+ * Unenroll a previously enrolled multi-factor authentication factor.
675
+ * @param option The multi-factor option to unenroll.
676
+ */
677
+ unenroll(option: MultiFactorInfo | string): Promise<void>;
599
678
  }
600
679
 
601
680
  /**
package/lib/index.js CHANGED
@@ -34,6 +34,7 @@ import {
34
34
  import ConfirmationResult from './ConfirmationResult';
35
35
  import PhoneAuthListener from './PhoneAuthListener';
36
36
  import PhoneMultiFactorGenerator from './PhoneMultiFactorGenerator';
37
+ import TotpMultiFactorGenerator from './TotpMultiFactorGenerator';
37
38
  import Settings from './Settings';
38
39
  import User from './User';
39
40
  import { getMultiFactorResolver } from './getMultiFactorResolver';
@@ -66,6 +67,7 @@ export {
66
67
  TwitterAuthProvider,
67
68
  FacebookAuthProvider,
68
69
  PhoneMultiFactorGenerator,
70
+ TotpMultiFactorGenerator,
69
71
  OAuthProvider,
70
72
  OIDCAuthProvider,
71
73
  PhoneAuthState,
@@ -80,6 +82,7 @@ const statics = {
80
82
  TwitterAuthProvider,
81
83
  FacebookAuthProvider,
82
84
  PhoneMultiFactorGenerator,
85
+ TotpMultiFactorGenerator,
83
86
  OAuthProvider,
84
87
  OIDCAuthProvider,
85
88
  PhoneAuthState,
@@ -324,6 +327,12 @@ class FirebaseAuthModule extends FirebaseModule {
324
327
  });
325
328
  }
326
329
 
330
+ resolveTotpSignIn(session, uid, totpSecret) {
331
+ return this.native.resolveTotpSignIn(session, uid, totpSecret).then(userCredential => {
332
+ return this._setUserCredential(userCredential);
333
+ });
334
+ }
335
+
327
336
  createUserWithEmailAndPassword(email, password) {
328
337
  return this.native
329
338
  .createUserWithEmailAndPassword(email, password)
@@ -26,14 +26,25 @@ export class MultiFactorUser {
26
26
  * profile, which is necessary to see the multi-factor changes.
27
27
  */
28
28
  async enroll(multiFactorAssertion, displayName) {
29
- const { token, secret } = multiFactorAssertion;
30
- await this._auth.native.finalizeMultiFactorEnrollment(token, secret, displayName);
29
+ const { token, secret, totpSecret, verificationCode } = multiFactorAssertion;
30
+ if (token && secret) {
31
+ await this._auth.native.finalizeMultiFactorEnrollment(token, secret, displayName);
32
+ } else if (totpSecret && verificationCode) {
33
+ await this._auth.native.finalizeTotpEnrollment(totpSecret, verificationCode, displayName);
34
+ } else {
35
+ throw new Error('Invalid multi-factor assertion provided for enrollment.');
36
+ }
31
37
 
32
38
  // We need to reload the user otherwise the changes are not visible
39
+ // TODO reload not working on Other platform
33
40
  return reload(this._auth.currentUser);
34
41
  }
35
42
 
36
- unenroll() {
37
- return Promise.reject(new Error('No implemented yet.'));
43
+ async unenroll(enrollmentId) {
44
+ await this._auth.native.unenrollMultiFactor(enrollmentId);
45
+
46
+ if (this._auth.currentUser) {
47
+ return reload(this._auth.currentUser);
48
+ }
38
49
  }
39
50
  }
package/lib/version.js CHANGED
@@ -1,2 +1,2 @@
1
1
  // Generated by genversion.
2
- module.exports = '23.1.2';
2
+ module.exports = '23.2.1';
@@ -8,6 +8,8 @@ import {
8
8
  sendSignInLinkToEmail,
9
9
  getAdditionalUserInfo,
10
10
  multiFactor,
11
+ getMultiFactorResolver,
12
+ TotpMultiFactorGenerator,
11
13
  createUserWithEmailAndPassword,
12
14
  signInWithEmailAndPassword,
13
15
  isSignInWithEmailLink,
@@ -70,6 +72,15 @@ function rejectPromiseWithCodeAndMessage(code, message) {
70
72
  return rejectPromise(getWebError({ code: `auth/${code}`, message }));
71
73
  }
72
74
 
75
+ function rejectWithCodeAndMessage(code, message) {
76
+ return Promise.reject(
77
+ getWebError({
78
+ code,
79
+ message,
80
+ }),
81
+ );
82
+ }
83
+
73
84
  /**
74
85
  * Returns a structured error object.
75
86
  * @param {error} error The error object.
@@ -102,7 +113,9 @@ function userToObject(user) {
102
113
  tenantId: user.tenantId !== null && user.tenantId !== '' ? user.tenantId : null,
103
114
  providerData: user.providerData.map(userInfoToObject),
104
115
  metadata: userMetadataToObject(user.metadata),
105
- multiFactor: multiFactor(user).enrolledFactors.map(multiFactorInfoToObject),
116
+ multiFactor: {
117
+ enrolledFactors: multiFactor(user).enrolledFactors.map(multiFactorInfoToObject),
118
+ },
106
119
  };
107
120
  }
108
121
 
@@ -222,6 +235,7 @@ const instances = {};
222
235
  const authStateListeners = {};
223
236
  const idTokenListeners = {};
224
237
  const sessionMap = new Map();
238
+ const totpSecretMap = new Map();
225
239
  let sessionId = 0;
226
240
 
227
241
  // Returns a cached Firestore instance.
@@ -441,11 +455,28 @@ export default {
441
455
  * @returns {Promise<object>} - The result of the sign in.
442
456
  */
443
457
  async signInWithEmailAndPassword(appName, email, password) {
444
- return guard(async () => {
445
- const auth = getCachedAuthInstance(appName);
446
- const credential = await signInWithEmailAndPassword(auth, email, password);
458
+ // The default guard / getWebError process doesn't work well here,
459
+ // since it creates a new error object that is then passed through
460
+ // a native module proxy and gets processed again.
461
+ // We need lots of information from the error so that MFA will work
462
+ // later if needed. So we handle the error custom here.
463
+ // return guard(async () => {
464
+ try {
465
+ const credential = await signInWithEmailAndPassword(
466
+ getCachedAuthInstance(appName),
467
+ email,
468
+ password,
469
+ );
447
470
  return authResultToObject(credential);
448
- });
471
+ } catch (e) {
472
+ e.userInfo = {
473
+ code: e.code.split('/')[1],
474
+ message: e.message,
475
+ customData: e.customData,
476
+ };
477
+ throw e;
478
+ }
479
+ // });
449
480
  },
450
481
 
451
482
  /**
@@ -991,6 +1022,104 @@ export default {
991
1022
  });
992
1023
  },
993
1024
 
1025
+ /**
1026
+ * Get a MultiFactorResolver from the underlying SDK
1027
+ * @param {*} _appName the name of the app to get the auth instance for
1028
+ * @param {*} uid the uid of the TOTP MFA attempt
1029
+ * @param {*} code the code from the user TOTP app
1030
+ * @return TotpMultiFactorAssertion to use for resolving
1031
+ */
1032
+ assertionForSignIn(_appName, uid, code) {
1033
+ return TotpMultiFactorGenerator.assertionForSignIn(uid, code);
1034
+ },
1035
+
1036
+ /**
1037
+ * Get a MultiFactorResolver from the underlying SDK
1038
+ * @param {*} appName the name of the app to get the auth instance for
1039
+ * @param {*} error the MFA error returned from initial factor login attempt
1040
+ * @return MultiFactorResolver to use for verifying the second factor
1041
+ */
1042
+ getMultiFactorResolver(appName, error) {
1043
+ return getMultiFactorResolver(getCachedAuthInstance(appName), error);
1044
+ },
1045
+
1046
+ /**
1047
+ * generate a TOTP secret
1048
+ * @param {*} _appName - The name of the app to get the auth instance for.
1049
+ * @param {*} session - The MultiFactorSession to associate with the secret
1050
+ * @returns object with secretKey to associate with TotpSecret
1051
+ */
1052
+ async generateTotpSecret(_appName, session) {
1053
+ return guard(async () => {
1054
+ const totpSecret = await TotpMultiFactorGenerator.generateSecret(sessionMap.get(session));
1055
+ totpSecretMap.set(totpSecret.secretKey, totpSecret);
1056
+ return { secretKey: totpSecret.secretKey };
1057
+ });
1058
+ },
1059
+
1060
+ /**
1061
+ * unenroll from TOTP
1062
+ * @param {*} appName - The name of the app to get the auth instance for.
1063
+ * @param {*} enrollmentId - The ID to associate with the enrollment
1064
+ * @returns
1065
+ */
1066
+ async unenrollMultiFactor(appName, enrollmentId) {
1067
+ return guard(async () => {
1068
+ const auth = getCachedAuthInstance(appName);
1069
+ if (auth.currentUser === null) {
1070
+ return promiseNoUser(true);
1071
+ }
1072
+ await multiFactor(auth.currentUser).unenroll(enrollmentId);
1073
+ });
1074
+ },
1075
+
1076
+ /**
1077
+ * finalize a TOTP enrollment
1078
+ * @param {*} appName - The name of the app to get the auth instance for.
1079
+ * @param {*} secretKey - The secretKey to associate native TotpSecret
1080
+ * @param {*} verificationCode - The TOTP to verify
1081
+ * @param {*} displayName - The name to associate as a hint
1082
+ * @returns
1083
+ */
1084
+ async finalizeTotpEnrollment(appName, secretKey, verificationCode, displayName) {
1085
+ return guard(async () => {
1086
+ const auth = getCachedAuthInstance(appName);
1087
+ if (auth.currentUser === null) {
1088
+ return promiseNoUser(true);
1089
+ }
1090
+ const multiFactorAssertion = TotpMultiFactorGenerator.assertionForEnrollment(
1091
+ totpSecretMap.get(secretKey),
1092
+ verificationCode,
1093
+ );
1094
+ await multiFactor(auth.currentUser).enroll(multiFactorAssertion, displayName);
1095
+ });
1096
+ },
1097
+
1098
+ /**
1099
+ * generate a TOTP QR Code URL
1100
+ * @param {*} _appName - The name of the app to get the auth instance for.
1101
+ * @param {*} secretKey - The secretKey to associate with the TotpSecret
1102
+ * @param {*} accountName - The account name to use in auth app
1103
+ * @param {*} issuer - The issuer to use in auth app
1104
+ * @returns QR Code URL
1105
+ */
1106
+ generateQrCodeUrl(_appName, secretKey, accountName, issuer) {
1107
+ return totpSecretMap.get(secretKey).generateQrCodeUrl(accountName, issuer);
1108
+ },
1109
+
1110
+ /**
1111
+ * open a QR Code URL in an app directly
1112
+ * @param {*} appName - The name of the app to get the auth instance for.
1113
+ * @param {*} qrCodeUrl the URL to open in the app, from generateQrCodeUrl
1114
+ * @throws Error not supported in this environment
1115
+ */
1116
+ openInOtpApp() {
1117
+ return rejectWithCodeAndMessage(
1118
+ 'unsupported',
1119
+ 'This operation is not supported in this environment.',
1120
+ );
1121
+ },
1122
+
994
1123
  /* ----------------------
995
1124
  * other methods
996
1125
  * ---------------------- */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@react-native-firebase/auth",
3
- "version": "23.1.2",
3
+ "version": "23.2.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",
@@ -27,7 +27,7 @@
27
27
  "plist": "^3.1.0"
28
28
  },
29
29
  "peerDependencies": {
30
- "@react-native-firebase/app": "23.1.2",
30
+ "@react-native-firebase/app": "23.2.1",
31
31
  "expo": ">=47.0.0"
32
32
  },
33
33
  "devDependencies": {
@@ -43,5 +43,5 @@
43
43
  "access": "public",
44
44
  "provenance": true
45
45
  },
46
- "gitHead": "65b4f41cea31be5393aca14bcc78ce6e5255f51f"
46
+ "gitHead": "79455d150fd162c23a1fd57af5d1a497303a5407"
47
47
  }