From ee7df855ec0a573df5aa2e26261adf9c292aa7d5 Mon Sep 17 00:00:00 2001 From: Mike Hardy Date: Thu, 19 Jan 2023 14:21:40 -0500 Subject: [PATCH] feat(app-check): add custom factory/provider; supports all providers On Android this allows use of Play Integrity provider. On iOS this allows use of AppAttest provider. On all platforms, things should be dynamically reconfigurable if you use our custom provider, and it should be easier to specify debug tokens for CI etc --- package.json | 4 +- packages/app-check/android/build.gradle | 1 + .../ReactNativeFirebaseAppCheckModule.java | 135 +++++++++++++----- .../ReactNativeFirebaseAppCheckProvider.java | 85 +++++++++++ ...NativeFirebaseAppCheckProviderFactory.java | 66 +++++++++ packages/app-check/e2e/appcheck.e2e.js | 103 ++++++++++++- .../RNFBAppCheck/RNFBAppCheckDebugProvider.h | 7 + .../ios/RNFBAppCheck/RNFBAppCheckModule.h | 6 + .../ios/RNFBAppCheck/RNFBAppCheckModule.m | 101 +++++++------ .../ios/RNFBAppCheck/RNFBAppCheckProvider.h | 33 +++++ .../ios/RNFBAppCheck/RNFBAppCheckProvider.m | 87 +++++++++++ .../RNFBAppCheckProviderFactory.h | 28 ++++ .../RNFBAppCheckProviderFactory.m | 64 +++++++++ .../ReactNativeFirebaseAppCheckProvider.js | 9 ++ packages/app-check/lib/index.d.ts | 86 +++++++++++ packages/app-check/lib/index.js | 69 +++++++-- tests/ios/testing/AppDelegate.mm | 10 +- 17 files changed, 795 insertions(+), 99 deletions(-) create mode 100644 packages/app-check/android/src/main/java/io/invertase/firebase/appcheck/ReactNativeFirebaseAppCheckProvider.java create mode 100644 packages/app-check/android/src/main/java/io/invertase/firebase/appcheck/ReactNativeFirebaseAppCheckProviderFactory.java create mode 100644 packages/app-check/ios/RNFBAppCheck/RNFBAppCheckDebugProvider.h create mode 100644 packages/app-check/ios/RNFBAppCheck/RNFBAppCheckProvider.h create mode 100644 packages/app-check/ios/RNFBAppCheck/RNFBAppCheckProvider.m create mode 100644 packages/app-check/ios/RNFBAppCheck/RNFBAppCheckProviderFactory.h create mode 100644 packages/app-check/ios/RNFBAppCheck/RNFBAppCheckProviderFactory.m create mode 100644 packages/app-check/lib/ReactNativeFirebaseAppCheckProvider.js diff --git a/package.json b/package.json index b6f3b6886c..6428796b66 100644 --- a/package.json +++ b/package.json @@ -30,8 +30,8 @@ "tests:emulator:start": "yarn tests:emulator:prepare && cd ./.github/workflows/scripts && ./start-firebase-emulator.sh --no-daemon", "tests:emulator:start:windows": "yarn tests:emulator:prepare && cd ./.github/workflows/scripts && ./start-firebase-emulator.bat --no-daemon", "tests:emulator:start-ci": "yarn tests:emulator:prepare && cd ./.github/workflows/scripts && ./start-firebase-emulator.sh", - "tests:android:build": "cd tests && cross-env FIREBASE_APP_CHECK_DEBUG_TOKEN=\"698956B2-187B-49C6-9E25-C3F3530EEBAF\" yarn detox build --configuration android.emu.debug", - "tests:android:build:windows": "cd tests && cross-env FIREBASE_APP_CHECK_DEBUG_TOKEN=\"698956B2-187B-49C6-9E25-C3F3530EEBAF\" yarn detox build --configuration android.emu.debug.windows", + "tests:android:build": "cd tests && yarn detox build --configuration android.emu.debug", + "tests:android:build:windows": "cd tests && yarn detox build --configuration android.emu.debug.windows", "tests:android:build-release": "cd tests && yarn detox build --configuration android.emu.release", "tests:android:test": "yarn tests:android:emulator:forward && cd tests && yarn detox test --configuration android.emu.debug", "tests:android:test:debug": "yarn tests:android:emulator:forward && cd tests && yarn detox test --configuration android.emu.debug --inspect", diff --git a/packages/app-check/android/build.gradle b/packages/app-check/android/build.gradle index f253ca2b05..3a97bbdee8 100644 --- a/packages/app-check/android/build.gradle +++ b/packages/app-check/android/build.gradle @@ -89,6 +89,7 @@ repositories { dependencies { api appProject implementation platform("com.google.firebase:firebase-bom:${ReactNative.ext.getVersion('firebase', 'bom')}") + implementation 'com.google.firebase:firebase-appcheck-playintegrity' implementation "com.google.firebase:firebase-appcheck-safetynet" implementation "com.google.firebase:firebase-appcheck-debug" } diff --git a/packages/app-check/android/src/main/java/io/invertase/firebase/appcheck/ReactNativeFirebaseAppCheckModule.java b/packages/app-check/android/src/main/java/io/invertase/firebase/appcheck/ReactNativeFirebaseAppCheckModule.java index 676d555eb7..5f99bb236a 100644 --- a/packages/app-check/android/src/main/java/io/invertase/firebase/appcheck/ReactNativeFirebaseAppCheckModule.java +++ b/packages/app-check/android/src/main/java/io/invertase/firebase/appcheck/ReactNativeFirebaseAppCheckModule.java @@ -23,65 +23,125 @@ import com.facebook.react.bridge.*; import com.google.android.gms.tasks.Tasks; import com.google.firebase.FirebaseApp; -import com.google.firebase.appcheck.AppCheckProviderFactory; import com.google.firebase.appcheck.FirebaseAppCheck; -import com.google.firebase.appcheck.debug.DebugAppCheckProviderFactory; -import com.google.firebase.appcheck.safetynet.SafetyNetAppCheckProviderFactory; +import io.invertase.firebase.common.ReactNativeFirebaseJSON; +import io.invertase.firebase.common.ReactNativeFirebaseMeta; import io.invertase.firebase.common.ReactNativeFirebaseModule; -import java.lang.reflect.*; +import io.invertase.firebase.common.ReactNativeFirebasePreferences; public class ReactNativeFirebaseAppCheckModule extends ReactNativeFirebaseModule { private static final String TAG = "AppCheck"; + private static final String LOGTAG = "RNFBAppCheck"; + private static final String KEY_APPCHECK_TOKEN_REFRESH_ENABLED = "app_check_token_auto_refresh"; + ReactNativeFirebaseAppCheckProviderFactory providerFactory = + new ReactNativeFirebaseAppCheckProviderFactory(); + + static boolean isAppCheckTokenRefreshEnabled() { + boolean enabled; + ReactNativeFirebaseJSON json = ReactNativeFirebaseJSON.getSharedInstance(); + ReactNativeFirebaseMeta meta = ReactNativeFirebaseMeta.getSharedInstance(); + ReactNativeFirebasePreferences prefs = ReactNativeFirebasePreferences.getSharedInstance(); + + if (prefs.contains(KEY_APPCHECK_TOKEN_REFRESH_ENABLED)) { + enabled = prefs.getBooleanValue(KEY_APPCHECK_TOKEN_REFRESH_ENABLED, true); + Log.d(LOGTAG, "isAppCheckCollectionEnabled via RNFBPreferences: " + enabled); + } else if (json.contains(KEY_APPCHECK_TOKEN_REFRESH_ENABLED)) { + enabled = json.getBooleanValue(KEY_APPCHECK_TOKEN_REFRESH_ENABLED, true); + Log.d(LOGTAG, "isAppCheckCollectionEnabled via RNFBJSON: " + enabled); + } else { + enabled = meta.getBooleanValue(KEY_APPCHECK_TOKEN_REFRESH_ENABLED, true); + Log.d(LOGTAG, "isAppCheckCollectionEnabled via RNFBMeta: " + enabled); + } + + if (BuildConfig.DEBUG) { + if (!json.getBooleanValue(KEY_APPCHECK_TOKEN_REFRESH_ENABLED, false)) { + enabled = false; + } + Log.d( + LOGTAG, + "isAppCheckTokenRefreshEnabled after checking " + + KEY_APPCHECK_TOKEN_REFRESH_ENABLED + + ": " + + enabled); + } + + Log.d(LOGTAG, "isAppCheckTokenRefreshEnabled final value: " + enabled); + return enabled; + } + + private boolean isAppDebuggable() throws Exception { + boolean isDebuggable = false; + PackageManager pm = getContext().getPackageManager(); + if (pm != null) { + isDebuggable = + (0 + != (pm.getApplicationInfo(getContext().getPackageName(), 0).flags + & ApplicationInfo.FLAG_DEBUGGABLE)); + } + Log.d(LOGTAG, "debuggable status? " + isDebuggable); + return isDebuggable; + } ReactNativeFirebaseAppCheckModule(ReactApplicationContext reactContext) { super(reactContext, TAG); + + // Our default token refresh config comes from config files, set it + FirebaseAppCheck firebaseAppCheck = FirebaseAppCheck.getInstance(); + firebaseAppCheck.setTokenAutoRefreshEnabled(isAppCheckTokenRefreshEnabled()); + } + + @ReactMethod + public void configureProvider( + String appName, String providerName, String debugToken, Promise promise) { + Log.d( + LOGTAG, + "configureProvider - appName/providerName/debugToken: " + + appName + + "/" + + providerName + + (debugToken != null ? "/(not shown)" : "/null")); + try { + providerFactory.configure(appName, providerName, debugToken); + FirebaseAppCheck.getInstance(FirebaseApp.getInstance(appName)) + .installAppCheckProviderFactory(providerFactory); + promise.resolve(null); + } catch (Exception e) { + rejectPromiseWithCodeAndMessage(promise, "unknown", "internal-error", e.getMessage()); + } } @ReactMethod public void activate( String appName, String siteKeyProvider, boolean isTokenAutoRefreshEnabled, Promise promise) { try { - FirebaseAppCheck firebaseAppCheck = FirebaseAppCheck.getInstance(); - firebaseAppCheck.setTokenAutoRefreshEnabled(isTokenAutoRefreshEnabled); - boolean isDebuggable = false; - PackageManager pm = getContext().getPackageManager(); - if (pm != null) { - isDebuggable = - (0 - != (pm.getApplicationInfo(getContext().getPackageName(), 0).flags - & ApplicationInfo.FLAG_DEBUGGABLE)); - } - if (isDebuggable) { + FirebaseAppCheck firebaseAppCheck = + FirebaseAppCheck.getInstance(FirebaseApp.getInstance(appName)); + firebaseAppCheck.setTokenAutoRefreshEnabled(isTokenAutoRefreshEnabled); + // Configure our new proxy factory in a backwards-compatible way for old API + if (isAppDebuggable()) { + Log.d(LOGTAG, "app is debuggable, configuring AppCheck for testing mode"); if (BuildConfig.FIREBASE_APP_CHECK_DEBUG_TOKEN != "null") { - // Get DebugAppCheckProviderFactory class - Class debugACFactoryClass = - DebugAppCheckProviderFactory.class; - - // Get the (undocumented) constructor accepting a debug token as string - Class[] argType = {String.class}; - Constructor c = debugACFactoryClass.getDeclaredConstructor(argType); - - // Create a object containing the constructor arguments - // and initialize a new instance. - Object[] cArgs = {BuildConfig.FIREBASE_APP_CHECK_DEBUG_TOKEN}; - firebaseAppCheck.installAppCheckProviderFactory( - (AppCheckProviderFactory) c.newInstance(cArgs)); + Log.d(LOGTAG, "debug app check token found in BuildConfig. Installing known token."); + providerFactory.configure(appName, "debug", BuildConfig.FIREBASE_APP_CHECK_DEBUG_TOKEN); } else { - firebaseAppCheck.installAppCheckProviderFactory( - DebugAppCheckProviderFactory.getInstance()); + Log.d( + LOGTAG, + "no debug app check token found in BuildConfig. Check Log for dynamic test token to" + + " configure in console."); + providerFactory.configure(appName, "debug", null); } - } else { - firebaseAppCheck.installAppCheckProviderFactory( - SafetyNetAppCheckProviderFactory.getInstance()); + providerFactory.configure(appName, "safetyNet", null); } + + FirebaseAppCheck.getInstance(FirebaseApp.getInstance(appName)) + .installAppCheckProviderFactory(providerFactory); + promise.resolve(null); } catch (Exception e) { - rejectPromiseWithCodeAndMessage(promise, "unknown", "internal-error", "unimplemented"); - return; + rejectPromiseWithCodeAndMessage(promise, "unknown", "internal-error", e.getMessage()); } - promise.resolve(null); } @ReactMethod @@ -92,6 +152,7 @@ public void setTokenAutoRefreshEnabled(String appName, boolean isTokenAutoRefres @ReactMethod public void getToken(String appName, boolean forceRefresh, Promise promise) { + Log.d(LOGTAG, "getToken appName/forceRefresh: " + appName + "/" + forceRefresh); FirebaseApp firebaseApp = FirebaseApp.getInstance(appName); Tasks.call( getExecutor(), @@ -108,7 +169,7 @@ public void getToken(String appName, boolean forceRefresh, Promise promise) { promise.resolve(tokenResultMap); } else { Log.e( - TAG, + LOGTAG, "RNFB: Unknown error while fetching AppCheck token " + task.getException().getMessage()); rejectPromiseWithCodeAndMessage( diff --git a/packages/app-check/android/src/main/java/io/invertase/firebase/appcheck/ReactNativeFirebaseAppCheckProvider.java b/packages/app-check/android/src/main/java/io/invertase/firebase/appcheck/ReactNativeFirebaseAppCheckProvider.java new file mode 100644 index 0000000000..41ff632107 --- /dev/null +++ b/packages/app-check/android/src/main/java/io/invertase/firebase/appcheck/ReactNativeFirebaseAppCheckProvider.java @@ -0,0 +1,85 @@ +package io.invertase.firebase.appcheck; + +/* + * Copyright (c) 2023-present Invertase Limited & Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this library except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +import android.util.Log; +import com.google.android.gms.tasks.Task; +import com.google.firebase.FirebaseApp; +import com.google.firebase.appcheck.AppCheckProvider; +import com.google.firebase.appcheck.AppCheckProviderFactory; +import com.google.firebase.appcheck.AppCheckToken; +import com.google.firebase.appcheck.debug.DebugAppCheckProviderFactory; +import com.google.firebase.appcheck.playintegrity.PlayIntegrityAppCheckProviderFactory; +import com.google.firebase.appcheck.safetynet.SafetyNetAppCheckProviderFactory; +import java.lang.reflect.Constructor; + +// Facade for all possible provider factory delegates, +// configurable dynamically instead of at startup +public class ReactNativeFirebaseAppCheckProvider implements AppCheckProvider { + private static final String LOGTAG = "RNFBAppCheck"; + + AppCheckProvider delegateProvider; + + @Override + public Task getToken() { + Log.d(LOGTAG, "Provider::getToken - delegating to native provider"); + return delegateProvider.getToken(); + } + + public void configure(String appName, String providerName, String debugToken) { + Log.d( + LOGTAG, + "Provider::configure with appName/providerName/debugToken: " + + appName + + "/" + + providerName + + "/" + + (debugToken != null ? "(not shown)" : "null")); + + try { + FirebaseApp app = FirebaseApp.getInstance(appName); + + if ("debug".equals(providerName)) { + + // the debug configuration may have a token, or may not + if (debugToken != null) { + // Create a debug provider using hidden factory constructor and our debug token + Class debugACFactoryClass = + DebugAppCheckProviderFactory.class; + Class[] argType = {String.class}; + Constructor c = debugACFactoryClass.getDeclaredConstructor(argType); + Object[] cArgs = {debugToken}; + delegateProvider = ((AppCheckProviderFactory) c.newInstance(cArgs)).create(app); + } else { + delegateProvider = DebugAppCheckProviderFactory.getInstance().create(app); + } + } + + if ("safetyNet".equals(providerName)) { + delegateProvider = SafetyNetAppCheckProviderFactory.getInstance().create(app); + } + + if ("playIntegrity".equals(providerName)) { + delegateProvider = PlayIntegrityAppCheckProviderFactory.getInstance().create(app); + } + } catch (Exception e) { + // This will bubble up and result in a rejected promise with the underlying message + throw new RuntimeException(e.getMessage()); + } + } +} diff --git a/packages/app-check/android/src/main/java/io/invertase/firebase/appcheck/ReactNativeFirebaseAppCheckProviderFactory.java b/packages/app-check/android/src/main/java/io/invertase/firebase/appcheck/ReactNativeFirebaseAppCheckProviderFactory.java new file mode 100644 index 0000000000..46dcdbe212 --- /dev/null +++ b/packages/app-check/android/src/main/java/io/invertase/firebase/appcheck/ReactNativeFirebaseAppCheckProviderFactory.java @@ -0,0 +1,66 @@ +package io.invertase.firebase.appcheck; + +/* + * Copyright (c) 2023-present Invertase Limited & Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this library except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +import android.util.Log; +import com.google.firebase.FirebaseApp; +import com.google.firebase.appcheck.AppCheckProvider; +import com.google.firebase.appcheck.AppCheckProviderFactory; +import java.util.HashMap; +import java.util.Map; + +public class ReactNativeFirebaseAppCheckProviderFactory implements AppCheckProviderFactory { + private static final String LOGTAG = "RNFBAppCheck"; + + // This object has one job - create + maintain control over one provider per app + public Map providers = new HashMap(); + + // Our provider will serve as a facade to all the supported native providers, + // we will just pass through configuration calls to it + public void configure(String appName, String providerName, String debugToken) { + Log.d( + LOGTAG, + "ProviderFactory::configure - appName/providerName/debugToken: " + + appName + + "/" + + providerName + + (debugToken != null ? "/(not shown)" : "/null")); + + ReactNativeFirebaseAppCheckProvider provider = null; + + // Look up the correct provider for the given appName, create it if not created + provider = providers.get(appName); + if (provider == null) { + provider = new ReactNativeFirebaseAppCheckProvider(); + providers.put(appName, provider); + } + provider.configure(appName, providerName, debugToken); + } + + public AppCheckProvider create(FirebaseApp firebaseApp) { + String appName = firebaseApp.getName(); + Log.d(LOGTAG, "ProviderFactory::create - fetching provider for app " + appName); + ReactNativeFirebaseAppCheckProvider provider = providers.get(appName); + if (provider == null) { + Log.d(LOGTAG, "ProviderFactory::create - provider not configured for this app."); + throw new RuntimeException( + "ReactNativeFirebaseAppCheckProvider not configured for app " + appName); + } + return provider; + } +} diff --git a/packages/app-check/e2e/appcheck.e2e.js b/packages/app-check/e2e/appcheck.e2e.js index dcd3ae2d02..440c1925b6 100644 --- a/packages/app-check/e2e/appcheck.e2e.js +++ b/packages/app-check/e2e/appcheck.e2e.js @@ -18,6 +18,28 @@ const jwt = require('jsonwebtoken'); describe('appCheck()', function () { + before(function () { + rnfbProvider = firebase.appCheck().newReactNativeFirebaseAppCheckProvider(); + rnfbProvider.configure({ + android: { + provider: 'debug', + debugToken: '698956B2-187B-49C6-9E25-C3F3530EEBAF', + }, + apple: { + provider: 'debug', + }, + web: { + provider: 'debug', + siteKey: 'none', + }, + }); + + // Our tests configure a debug provider with shared secret so we should get a valid token + firebase + .appCheck() + .initializeAppCheck({ provider: rnfbProvider, isTokenAutoRefreshEnabled: false }); + }); + describe('config', function () { // This depends on firebase.json containing a false value for token auto refresh, we // verify here that it was carried in to the Info.plist correctly @@ -36,15 +58,30 @@ describe('appCheck()', function () { }); describe('setTokenAutoRefresh())', function () { - it('should set token refresh', function () { + it('should set token refresh', async function () { firebase.appCheck().setTokenAutoRefreshEnabled(false); + + // Only iOS lets us assert on this unfortunately, other platforms have no accessor + if (device.getPlatform() === 'ios') { + let tokenRefresh = await NativeModules.RNFBAppCheckModule.isTokenAutoRefreshEnabled( + '[DEFAULT]', + ); + tokenRefresh.should.equal(false); + } + firebase.appCheck().setTokenAutoRefreshEnabled(true); + // Only iOS lets us assert on this unfortunately, other platforms have no accessor + if (device.getPlatform() === 'ios') { + tokenRefresh = await NativeModules.RNFBAppCheckModule.isTokenAutoRefreshEnabled( + '[DEFAULT]', + ); + tokenRefresh.should.equal(true); + } }); }); + describe('getToken())', function () { - it('token fetch attempt should work', async function () { - await firebase.appCheck().activate('ignored', false); - // Our tests configure a debug provider with shared secret so we should get a valid token - const { token } = await firebase.appCheck().getToken(); + it('token fetch attempt with configured debug token should work', async function () { + const { token } = await firebase.appCheck().getToken(true); token.should.not.equal(''); const decodedToken = jwt.decode(token); decodedToken.aud[1].should.equal('projects/react-native-firebase-testing'); @@ -52,6 +89,9 @@ describe('appCheck()', function () { Promise.reject('Token already expired'); } + // on android if you move too fast, you may not get a fresh token + await Utils.sleep(2000); + // Force refresh should get a different token? // TODO sometimes fails on android https://github.com/firebase/firebase-android-sdk/issues/2954 const { token: token2 } = await firebase.appCheck().getToken(true); @@ -63,7 +103,60 @@ describe('appCheck()', function () { } (token === token2).should.be.false(); }); + + it('token fetch with config switch to invalid then valid should fail then work', async function () { + rnfbProvider = firebase.appCheck().newReactNativeFirebaseAppCheckProvider(); + rnfbProvider.configure({ + android: { + provider: 'playIntegrity', + }, + apple: { + provider: 'appAttest', + }, + web: { + provider: 'debug', + siteKey: 'none', + }, + }); + + // Our tests configure a debug provider with shared secret so we should get a valid token + firebase + .appCheck() + .initializeAppCheck({ provider: rnfbProvider, isTokenAutoRefreshEnabled: false }); + try { + await firebase.appCheck().getToken(true); + return Promise.reject(new Error('should have thrown an error')); + } catch (e) { + e.message.should.containEql('appCheck/token-error'); + } + + rnfbProvider.configure({ + android: { + provider: 'debug', + debugToken: '698956B2-187B-49C6-9E25-C3F3530EEBAF', + }, + apple: { + provider: 'debug', + }, + web: { + provider: 'debug', + siteKey: 'none', + }, + }); + firebase + .appCheck() + .initializeAppCheck({ provider: rnfbProvider, isTokenAutoRefreshEnabled: false }); + + const { token } = await firebase.appCheck().getToken(true); + token.should.not.equal(''); + const decodedToken = jwt.decode(token); + decodedToken.aud[1].should.equal('projects/react-native-firebase-testing'); + if (decodedToken.exp < Date.now()) { + Promise.reject('Token already expired'); + } + }); }); + describe('activate())', function () { it('should activate with default provider and defined token refresh', function () { firebase diff --git a/packages/app-check/ios/RNFBAppCheck/RNFBAppCheckDebugProvider.h b/packages/app-check/ios/RNFBAppCheck/RNFBAppCheckDebugProvider.h new file mode 100644 index 0000000000..82a496cf77 --- /dev/null +++ b/packages/app-check/ios/RNFBAppCheck/RNFBAppCheckDebugProvider.h @@ -0,0 +1,7 @@ +// Subclass FIRAppCheckDebugProvider here +// https://developer.apple.com/library/archive/documentation/General/Conceptual/DevPedia-CocoaCore/MethodOverriding.html + +// 1. have a new nullable configuredDebugToken property +// 2. override implementation of currentDebutToken to: +// - return configuredDebugToken if it exists, +// - else return [super currentDebugToken] if not diff --git a/packages/app-check/ios/RNFBAppCheck/RNFBAppCheckModule.h b/packages/app-check/ios/RNFBAppCheck/RNFBAppCheckModule.h index bd8fe9a23c..999f403a6e 100644 --- a/packages/app-check/ios/RNFBAppCheck/RNFBAppCheckModule.h +++ b/packages/app-check/ios/RNFBAppCheck/RNFBAppCheckModule.h @@ -19,6 +19,12 @@ #import +#import "RNFBAppCheckProviderFactory.h" + @interface RNFBAppCheckModule : NSObject +@property RNFBAppCheckProviderFactory* _Nullable providerFactory; + ++ (_Nonnull instancetype)sharedInstance; + @end diff --git a/packages/app-check/ios/RNFBAppCheck/RNFBAppCheckModule.m b/packages/app-check/ios/RNFBAppCheck/RNFBAppCheckModule.m index 672cc87dc8..f962a7cb69 100644 --- a/packages/app-check/ios/RNFBAppCheck/RNFBAppCheckModule.m +++ b/packages/app-check/ios/RNFBAppCheck/RNFBAppCheckModule.m @@ -33,6 +33,17 @@ - (dispatch_queue_t)methodQueue { return dispatch_get_main_queue(); } ++ (instancetype)sharedInstance { + static dispatch_once_t once; + __strong static RNFBAppCheckModule *sharedInstance; + dispatch_once(&once, ^{ + sharedInstance = [[RNFBAppCheckModule alloc] init]; + sharedInstance.providerFactory = [[RNFBAppCheckProviderFactory alloc] init]; + [FIRAppCheck setAppCheckProviderFactory:sharedInstance.providerFactory]; + }); + return sharedInstance; +} + #pragma mark - #pragma mark Firebase AppCheck Methods @@ -42,21 +53,35 @@ - (dispatch_queue_t)methodQueue { : (BOOL)isTokenAutoRefreshEnabled : (RCTPromiseResolveBlock)resolve rejecter : (RCTPromiseRejectBlock)reject) { - // From SDK docs: - // NOTE: Make sure to call this method before FirebaseApp.configure(). - // If this method is called after configuring Firebase, the changes will not take effect. - - // So in react-native-firebase we will only use this to set the isTokenAutoRefreshEnabled - // parameter, but if AppCheck is included on iOS it wlil be active with DeviceCheckProviderFactory + DLog(@"deprecated API, provider will be deviceCheck / token refresh %d for app %@", + isTokenAutoRefreshEnabled, firebaseApp.name); + [[RNFBAppCheckModule sharedInstance].providerFactory configure:firebaseApp + providerName:@"deviceCheck" + debugToken:nil]; FIRAppCheck *appCheck = [FIRAppCheck appCheckWithApp:firebaseApp]; appCheck.isTokenAutoRefreshEnabled = isTokenAutoRefreshEnabled; resolve([NSNull null]); } +RCT_EXPORT_METHOD(configureProvider + : (FIRApp *)firebaseApp + : (nonnull NSString *)providerName + : (nullable NSString *)debugToken + : (RCTPromiseResolveBlock)resolve rejecter + : (RCTPromiseRejectBlock)reject) { + DLog(@"appName/providerName/debugToken: %@/%@/%@", firebaseApp.name, providerName, + (debugToken == nil ? @"null" : @"(not shown)")); + [[RNFBAppCheckModule sharedInstance].providerFactory configure:firebaseApp + providerName:providerName + debugToken:debugToken]; + resolve([NSNull null]); +} + RCT_EXPORT_METHOD(setTokenAutoRefreshEnabled : (FIRApp *)firebaseApp : (BOOL)isTokenAutoRefreshEnabled) { + DLog(@"app/isTokenAutoRefreshEnabled: %@/%d", firebaseApp.name, isTokenAutoRefreshEnabled); FIRAppCheck *appCheck = [FIRAppCheck appCheckWithApp:firebaseApp]; appCheck.isTokenAutoRefreshEnabled = isTokenAutoRefreshEnabled; } @@ -69,6 +94,7 @@ - (dispatch_queue_t)methodQueue { : (RCTPromiseRejectBlock)reject) { FIRAppCheck *appCheck = [FIRAppCheck appCheckWithApp:firebaseApp]; BOOL isTokenAutoRefreshEnabled = appCheck.isTokenAutoRefreshEnabled; + DLog(@"app/isTokenAutoRefreshEnabled: %@/%d", firebaseApp.name, isTokenAutoRefreshEnabled); resolve([NSNumber numberWithBool:isTokenAutoRefreshEnabled]); } @@ -78,41 +104,34 @@ - (dispatch_queue_t)methodQueue { : (RCTPromiseResolveBlock)resolve : (RCTPromiseRejectBlock)reject) { FIRAppCheck *appCheck = [FIRAppCheck appCheckWithApp:firebaseApp]; - [appCheck tokenForcingRefresh:forceRefresh - completion:^(FIRAppCheckToken *_Nullable token, NSError *_Nullable error) { - if (error != nil) { - // Handle any errors if the token was not retrieved. - DLog(@"Unable to retrieve App Check token: %@", error); - [RNFBSharedUtils - rejectPromiseWithUserInfo:reject - userInfo:(NSMutableDictionary *)@{ - @"code" : @"token-error", - @"message" : [error localizedDescription], - }]; - return; - } - if (token == nil) { - DLog(@"Unable to retrieve App Check token."); - [RNFBSharedUtils rejectPromiseWithUserInfo:reject - userInfo:(NSMutableDictionary *)@{ - @"code" : @"token-null", - @"message" : @"no token fetched", - }]; - return; - } - - NSMutableDictionary *tokenResultDictionary = [NSMutableDictionary new]; - tokenResultDictionary[@"token"] = token.token; - resolve(tokenResultDictionary); - }]; + DLog(@"appName %@", firebaseApp.name); + [appCheck + tokenForcingRefresh:forceRefresh + completion:^(FIRAppCheckToken *_Nullable token, NSError *_Nullable error) { + if (error != nil) { + // Handle any errors if the token was not retrieved. + DLog(@"RNFBAppCheck - getToken - Unable to retrieve App Check token: %@", error); + [RNFBSharedUtils rejectPromiseWithUserInfo:reject + userInfo:(NSMutableDictionary *)@{ + @"code" : @"token-error", + @"message" : [error localizedDescription], + }]; + return; + } + if (token == nil) { + DLog(@"RNFBAppCheck - getToken - Unable to retrieve App Check token."); + [RNFBSharedUtils rejectPromiseWithUserInfo:reject + userInfo:(NSMutableDictionary *)@{ + @"code" : @"token-null", + @"message" : @"no token fetched", + }]; + return; + } + + NSMutableDictionary *tokenResultDictionary = [NSMutableDictionary new]; + tokenResultDictionary[@"token"] = token.token; + resolve(tokenResultDictionary); + }]; } -// TODO -// - set up DeviceCheckProvider and Debug provider -// FIRAppCheckDebugProviderFactory *providerFactory = -// [[FIRAppCheckDebugProviderFactory alloc] init]; -// [FIRAppCheck setAppCheckProviderFactory:providerFactory]; - -// Write a custom provider factory, and allow the AppAttest provider - @end diff --git a/packages/app-check/ios/RNFBAppCheck/RNFBAppCheckProvider.h b/packages/app-check/ios/RNFBAppCheck/RNFBAppCheckProvider.h new file mode 100644 index 0000000000..e1877a1d9d --- /dev/null +++ b/packages/app-check/ios/RNFBAppCheck/RNFBAppCheckProvider.h @@ -0,0 +1,33 @@ +/** + * Copyright (c) 2023-present Invertase Limited & Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this library except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +#import +#import + +@interface RNFBAppCheckProvider : NSObject + +@property FIRApp *app; + +@property id delegateProvider; + +- (void)configure:(FIRApp *)app + providerName:(NSString *)providerName + debugToken:(NSString *)debugToken; + +- (id)initWithApp:(FIRApp *)app; + +@end diff --git a/packages/app-check/ios/RNFBAppCheck/RNFBAppCheckProvider.m b/packages/app-check/ios/RNFBAppCheck/RNFBAppCheckProvider.m new file mode 100644 index 0000000000..6415b7048e --- /dev/null +++ b/packages/app-check/ios/RNFBAppCheck/RNFBAppCheckProvider.m @@ -0,0 +1,87 @@ +/** + * Copyright (c) 2023-present Invertase Limited & Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this library except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +#import "RNFBAppCheckProvider.h" +#import "RNFBApp/RNFBSharedUtils.h" + +@implementation RNFBAppCheckProvider + +- (id)initWithApp:app { + DLog(@"RNFBAppCheck init with app %@", [app name]); + self = [super init]; + if (self) { + self.app = app; + } + return self; +} + +- (void)configure:(FIRApp *)app + providerName:(NSString *)providerName + debugToken:(NSString *)debugToken { + DLog(@"appName/providerName/debugToken: %@/%@/%@", app.name, providerName, + (debugToken == nil ? @"null" : @"(not shown)")); + + DLog(@"appName %@", app.name); + + // - determine if debugToken is provided via nullable arg + if ([providerName isEqualToString:@"debug"]) { + // TODO: Currently not handling debugToken argument, relying on existing environment + // variable configuration style. + // - maybe directly setting an environment variable could work? + // https://stackoverflow.com/questions/27139589/whats-the-idiomatic-way-of-setting-an-environment-variable-in-objective-c-coco + // - ...otherwise if env var does not work + // - subclass style: RNFBAppCheckDebugProvider, and we should print local token + // - if a debugToken parameter was supplied, set + // RNFBAppCheckDebugProvider.configuredDebugToken + // - print local token + // https://github.com/firebase/firebase-ios-sdk/blob/c7e95996ff/FirebaseAppCheck/Sources/DebugProvider/FIRAppCheckDebugProviderFactory.m + // - print if current token in provided by configuration, by environment variable, or local + // token? + + self.delegateProvider = [[FIRAppCheckDebugProvider new] initWithApp:app]; + } + + if ([providerName isEqualToString:@"deviceCheck"]) { + self.delegateProvider = [[FIRDeviceCheckProvider new] initWithApp:app]; + } + + if ([providerName isEqualToString:@"appAttest"]) { + if (@available(iOS 14.0, macCatalyst 14.0, tvOS 15.0, watchOS 9.0, *)) { + self.delegateProvider = [[FIRAppAttestProvider alloc] initWithApp:app]; + } else { + // This is not a valid configuration. + DLog(@"AppAttest unavailable: it requires iOS14+, macCatalyst14+ or tvOS15+. Installing " + @"debug provider to guarantee invalid tokens in this invalid configuration."); + self.delegateProvider = [[FIRAppCheckDebugProvider new] initWithApp:app]; + } + } + + if ([providerName isEqualToString:@"appAttestWithDeviceCheckFallback"]) { + if (@available(iOS 14.0, *)) { + self.delegateProvider = [[FIRAppAttestProvider alloc] initWithApp:app]; + } else { + self.delegateProvider = [[FIRDeviceCheckProvider alloc] initWithApp:app]; + } + } +} + +- (void)getTokenWithCompletion:(nonnull void (^)(FIRAppCheckToken *_Nullable, + NSError *_Nullable))handler { + DLog(@"proxying to delegateProvider..."); + [self.delegateProvider getTokenWithCompletion:handler]; +} + +@end diff --git a/packages/app-check/ios/RNFBAppCheck/RNFBAppCheckProviderFactory.h b/packages/app-check/ios/RNFBAppCheck/RNFBAppCheckProviderFactory.h new file mode 100644 index 0000000000..0fae9ab05c --- /dev/null +++ b/packages/app-check/ios/RNFBAppCheck/RNFBAppCheckProviderFactory.h @@ -0,0 +1,28 @@ +/** + * Copyright (c) 2023-present Invertase Limited & Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this library except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +#import + +@interface RNFBAppCheckProviderFactory : NSObject + +@property NSMutableDictionary *_Nullable providers; + +- (void)configure:(FIRApp *_Nonnull)app + providerName:(NSString *_Nonnull)providerName + debugToken:(NSString *_Nullable)debugToken; + +@end diff --git a/packages/app-check/ios/RNFBAppCheck/RNFBAppCheckProviderFactory.m b/packages/app-check/ios/RNFBAppCheck/RNFBAppCheckProviderFactory.m new file mode 100644 index 0000000000..5fcf9a4451 --- /dev/null +++ b/packages/app-check/ios/RNFBAppCheck/RNFBAppCheckProviderFactory.m @@ -0,0 +1,64 @@ +/** + * Copyright (c) 2023-present Invertase Limited & Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this library except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +#import "RNFBAppCheckProviderFactory.h" +#import +#import +#import "RNFBApp/RNFBSharedUtils.h" +#import "RNFBAppCheckProvider.h" + +@implementation RNFBAppCheckProviderFactory + +- (nullable id)createProviderWithApp:(FIRApp *)app { + DLog(@"appName %@", app.name); + + // The SDK may try to call this before we have been configured, + // so we will configure ourselves and set the provider up as a default to start + // pre-configure + if (self.providers == nil) { + DLog(@"providers dictionary initializing for app %@", app.name); + self.providers = [NSMutableDictionary new]; + } + + if (self.providers[app.name] == nil) { + DLog(@"provider initializing (with default to debug) for app %@", app.name); + self.providers[app.name] = [RNFBAppCheckProvider new]; + RNFBAppCheckProvider *provider = self.providers[app.name]; + [provider configure:app providerName:@"debug" debugToken:nil]; + } + + return self.providers[app.name]; +} + +- (void)configure:(FIRApp *)app + providerName:(NSString *)providerName + debugToken:(NSString *)debugToken { + DLog(@"appName/providerName/debugToken: %@/%@/%@", app.name, providerName, + (debugToken == nil ? @"null" : @"(not shown)")); + if (self.providers == nil) { + self.providers = [NSMutableDictionary new]; + } + + if (self.providers[app.name] == nil) { + self.providers[app.name] = [RNFBAppCheckProvider new]; + } + + RNFBAppCheckProvider *provider = self.providers[app.name]; + [provider configure:app providerName:providerName debugToken:debugToken]; +} + +@end diff --git a/packages/app-check/lib/ReactNativeFirebaseAppCheckProvider.js b/packages/app-check/lib/ReactNativeFirebaseAppCheckProvider.js new file mode 100644 index 0000000000..b328d8aeba --- /dev/null +++ b/packages/app-check/lib/ReactNativeFirebaseAppCheckProvider.js @@ -0,0 +1,9 @@ +export default class ReactNativeFirebaseAppCheckProvider { + providerOptions; + + constructor() {} + + configure(options) { + this.providerOptions = options; + } +} diff --git a/packages/app-check/lib/index.d.ts b/packages/app-check/lib/index.d.ts index 7937a1164d..b26ade0cf4 100644 --- a/packages/app-check/lib/index.d.ts +++ b/packages/app-check/lib/index.d.ts @@ -68,6 +68,81 @@ export namespace FirebaseAppCheckTypes { getToken(): Promise; } + /** + * Options for App Check initialization. + */ + export interface AppCheckOptions { + /** + * A reCAPTCHA V3 provider, reCAPTCHA Enterprise provider, or custom provider. + * Note that in react-native-firebase provider should always be ReactNativeAppCheckCustomProvider, a cross-platform + * implementation of an AppCheck CustomProvider + */ + provider: CustomProvider | ReCaptchaV3Provider | ReCaptchaEnterpriseProvider; + + /** + * If true, enables SDK to automatically + * refresh AppCheck token as needed. If undefined, the value will default + * to the value of `app.automaticDataCollectionEnabled`. That property + * defaults to false and can be set in the app config. + */ + isTokenAutoRefreshEnabled?: boolean; + } + + export interface ReactNativeFirebaseAppCheckProviderOptions { + /** + * debug token to use, if any. Defaults to undefined, pre-configure tokens in firebase web console if needed + */ + debugToken?: string; + } + + export interface ReactNativeFirebaseAppCheckProviderWebOptions + extends ReactNativeFirebaseAppCheckProviderOptions { + /** + * The web provider to use, either `reCaptchaV3` or `reCaptchaEnterprise`, defaults to `reCaptchaV3` + */ + provider?: 'debug' | 'reCaptchaV3' | 'reCaptchaEnterprise'; + + /** + * siteKey for use in web queries, defaults to `none` + */ + siteKey?: string; + } + + export interface ReactNativeFirebaseAppCheckProviderAppleOptions + extends ReactNativeFirebaseAppCheckProviderOptions { + /** + * The apple provider to use, either `deviceCheck` or `appAttest`, or `appAttestWithDeviceCheckFallback`, + * defaults to `DeviceCheck`. `appAttest` requires iOS 14+ or will fail, `appAttestWithDeviceCheckFallback` + * will use `appAttest` for iOS14+ and fallback to `deviceCheck` on devices with ios13 and lower + */ + provider?: 'debug' | 'deviceCheck' | 'appAttest' | 'appAttestWithDeviceCheckFallback'; + } + + export interface ReactNativeFirebaseAppCheckProviderAndroidOptions + extends ReactNativeFirebaseAppCheckProviderOptions { + /** + * The android provider to use, either `safetyNet` or `playIntegrity`. default is `playIntegrity`, `safetyNet` is deprecated. + */ + provider?: 'debug' | 'safetyNet' | 'playIntegrity'; + } + + export interface ReactNativeFirebaseAppCheckProvider extends AppCheckProvider { + /** + * Specify how the app check provider should be configured. The new configuration is + * in effect when this call returns. You must call `getToken()` + * after this call to get a token using the new configuration. + * This custom provider allows for delayed configuration and re-configuration on all platforms + * so AppCheck has the same experience across all platforms, with the only difference being the native + * providers you choose to use on each platform. + */ + configure( + web?: ReactNativeFirebaseAppCheckProviderWebOptions, + android?: ReactNativeFirebaseAppCheckProviderAndroidOptions, + apple?: ReactNativeFirebaseAppCheckProviderAppleOptions, + isTokenAutoRefreshEnabled?: boolean, + ): Promise; + } + /** * Result returned by `getToken()`. */ @@ -118,6 +193,16 @@ export namespace FirebaseAppCheckTypes { * */ export class Module extends FirebaseModule { + /** + * initialize the AppCheck module. Note that in react-native-firebase AppCheckOptions must always + * be an object with a `provider` member containing `ReactNativeFirebaseAppCheckProvider` that has returned successfully + * from a call to the `configure` method, with sub-providers for the various platforms configured to meet your project + * requirements. This must be called prior to interacting with any firebase services protected by AppCheck + * + * @param options an AppCheckOptions with a configured ReactNativeFirebaseAppCheckProvider as the provider + */ + initializeAppCheck(options: AppCheckOptions): Promise; + /** * Activate App Check * On iOS App Check is activated with DeviceCheck provider simply by including the module, using the token auto refresh default or @@ -127,6 +212,7 @@ export namespace FirebaseAppCheckTypes { * On iOS if you want to set a specific AppCheckProviderFactory (for instance to FIRAppCheckDebugProviderFactory or * FIRAppAttestProvider) you must manually do that in your AppDelegate.m prior to calling [FIRApp configure] * + * @deprecated use initializeAppCheck to gain access to all platform providers and firebase-js-sdk v9 compatibility * @param siteKeyOrProvider - This is ignored, Android uses DebugProviderFactory if the app is debuggable (https://firebase.google.com/docs/app-check/android/debug-provider) * Android uses SafetyNetProviderFactory for release builds. * iOS uses DeviceCheckProviderFactory by default unless altered in AppDelegate.m manually diff --git a/packages/app-check/lib/index.js b/packages/app-check/lib/index.js index 210b9ffba2..9c2bdfba1c 100644 --- a/packages/app-check/lib/index.js +++ b/packages/app-check/lib/index.js @@ -21,6 +21,8 @@ import { FirebaseModule, getFirebaseRoot, } from '@react-native-firebase/app/lib/internal'; +import { Platform } from 'react-native'; +import ReactNativeFirebaseAppCheckProvider from './ReactNativeFirebaseAppCheckProvider'; import version from './version'; @@ -31,27 +33,70 @@ const namespace = 'appCheck'; const nativeModuleName = 'RNFBAppCheckModule'; class FirebaseAppCheckModule extends FirebaseModule { - activate(siteKeyOrProvider, isTokenAutoRefreshEnabled) { - if (!isString(siteKeyOrProvider)) { - throw new Error('siteKeyOrProvider must be a string value to match firebase-js-sdk API'); - } + getIsTokenRefreshEnabledDefault() { + // no default to start + isTokenAutoRefreshEnabled = undefined; - // If the caller did not specify token refresh, attempt to use app-check specific setting: - if (!isBoolean(isTokenAutoRefreshEnabled)) { - isTokenAutoRefreshEnabled = this.firebaseJson.app_check_token_auto_refresh; + return isTokenAutoRefreshEnabled; + } + + newReactNativeFirebaseAppCheckProvider() { + return new ReactNativeFirebaseAppCheckProvider(); + } + + initializeAppCheck(options) { + // determine token refresh setting, if not specified + if (!isBoolean(options.isTokenAutoRefreshEnabled)) { + options.isTokenAutoRefreshEnabled = this.firebaseJson.app_check_token_auto_refresh; } // If that was not defined, attempt to use app-wide data collection setting per docs: - if (!isBoolean(isTokenAutoRefreshEnabled)) { - isTokenAutoRefreshEnabled = this.firebaseJson.app_data_collection_default_enabled; + if (!isBoolean(options.isTokenAutoRefreshEnabled)) { + options.isTokenAutoRefreshEnabled = this.firebaseJson.app_data_collection_default_enabled; } // If that also was not defined, the default is documented as true. - if (!isBoolean(isTokenAutoRefreshEnabled)) { - isTokenAutoRefreshEnabled = true; + if (!isBoolean(options.isTokenAutoRefreshEnabled)) { + options.isTokenAutoRefreshEnabled = true; + } + this.native.setTokenAutoRefreshEnabled(options.isTokenAutoRefreshEnabled); + + if (Platform.OS === 'android') { + return this.native.configureProvider( + options.provider.providerOptions.android.provider, + options.provider.providerOptions.android.debugToken, + ); + } + if (Platform.OS === 'ios' || Platform.OS === 'macos') { + return this.native.configureProvider( + options.provider.providerOptions.apple.provider, + options.provider.providerOptions.apple.debugToken, + ); + } + throw new Error('Unsupported platform: ' + Platform.OS); + } + + activate(siteKeyOrProvider, isTokenAutoRefreshEnabled) { + if (!isString(siteKeyOrProvider)) { + throw new Error('siteKeyOrProvider must be a string value to match firebase-js-sdk API'); } - return this.native.activate(siteKeyOrProvider, isTokenAutoRefreshEnabled); + // We wrap our new flexible interface, with compatible defaults + rnfbProvider = new ReactNativeFirebaseAppCheckProvider(); + rnfbProvider.configure({ + android: { + provider: 'safetyNet', + }, + apple: { + provider: 'deviceCheck', + }, + web: { + provider: 'reCaptchaV3', + siteKey: 'none', + }, + }); + + return this.initializeAppCheck({ provider: rnfbProvider, isTokenAutoRefreshEnabled }); } setTokenAutoRefreshEnabled(isTokenAutoRefreshEnabled) { diff --git a/tests/ios/testing/AppDelegate.mm b/tests/ios/testing/AppDelegate.mm index 7ee78e2767..2d80aa7cf5 100644 --- a/tests/ios/testing/AppDelegate.mm +++ b/tests/ios/testing/AppDelegate.mm @@ -18,6 +18,7 @@ #import "AppDelegate.h" #import "RNFBMessagingModule.h" +#import "RNFBAppCheckModule.h" #import #import @@ -45,12 +46,17 @@ @interface AppDelegate () @implementation AppDelegate - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { + // Initialize RNFBAppCheckModule, it sets the custom RNFBAppCheckProviderFactory + // which lets us configure any of the available native platform providers, + // and reconfigure if needed, dynamically after `[FIRApp configure]` just like the other platforms. + [RNFBAppCheckModule sharedInstance]; + // Install the AppCheck debug provider so we may get tokens on iOS Simulators for testing. // See https://firebase.google.com/docs/app-check/ios/debug-provider for instructions on configuring a debug token // This *must* be done before the `[FIRApp configure]` line, so it must be done in AppDelegate for any app // that wants to enforce AppCheck restrictions on their backend while also doing testing on iOS Simulator. - FIRAppCheckDebugProviderFactory *providerFactory = [[FIRAppCheckDebugProviderFactory alloc] init]; - [FIRAppCheck setAppCheckProviderFactory:providerFactory]; +// FIRAppCheckDebugProviderFactory *providerFactory = [[FIRAppCheckDebugProviderFactory alloc] init]; +// [FIRAppCheck setAppCheckProviderFactory:providerFactory]; if ([FIRApp defaultApp] == nil) { [FIRApp configure];