@react-native-firebase/app-check 16.6.0 → 17.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -3,6 +3,16 @@
3
3
  All notable changes to this project will be documented in this file.
4
4
  See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
5
5
 
6
+ ## [17.0.0](https://github.com/invertase/react-native-firebase/compare/v16.7.0...v17.0.0) (2023-02-02)
7
+
8
+ ### Features
9
+
10
+ - **app-check:** add custom factory/provider; supports all providers ([ee7df85](https://github.com/invertase/react-native-firebase/commit/ee7df855ec0a573df5aa2e26261adf9c292aa7d5))
11
+
12
+ ## [16.7.0](https://github.com/invertase/react-native-firebase/compare/v16.6.0...v16.7.0) (2023-01-28)
13
+
14
+ **Note:** Version bump only for package @react-native-firebase/app-check
15
+
6
16
  ## [16.6.0](https://github.com/invertase/react-native-firebase/compare/v16.5.2...v16.6.0) (2023-01-27)
7
17
 
8
18
  **Note:** Version bump only for package @react-native-firebase/app-check
@@ -89,6 +89,7 @@ repositories {
89
89
  dependencies {
90
90
  api appProject
91
91
  implementation platform("com.google.firebase:firebase-bom:${ReactNative.ext.getVersion('firebase', 'bom')}")
92
+ implementation 'com.google.firebase:firebase-appcheck-playintegrity'
92
93
  implementation "com.google.firebase:firebase-appcheck-safetynet"
93
94
  implementation "com.google.firebase:firebase-appcheck-debug"
94
95
  }
@@ -23,65 +23,125 @@ import android.util.Log;
23
23
  import com.facebook.react.bridge.*;
24
24
  import com.google.android.gms.tasks.Tasks;
25
25
  import com.google.firebase.FirebaseApp;
26
- import com.google.firebase.appcheck.AppCheckProviderFactory;
27
26
  import com.google.firebase.appcheck.FirebaseAppCheck;
28
- import com.google.firebase.appcheck.debug.DebugAppCheckProviderFactory;
29
- import com.google.firebase.appcheck.safetynet.SafetyNetAppCheckProviderFactory;
27
+ import io.invertase.firebase.common.ReactNativeFirebaseJSON;
28
+ import io.invertase.firebase.common.ReactNativeFirebaseMeta;
30
29
  import io.invertase.firebase.common.ReactNativeFirebaseModule;
31
- import java.lang.reflect.*;
30
+ import io.invertase.firebase.common.ReactNativeFirebasePreferences;
32
31
 
33
32
  public class ReactNativeFirebaseAppCheckModule extends ReactNativeFirebaseModule {
34
33
  private static final String TAG = "AppCheck";
34
+ private static final String LOGTAG = "RNFBAppCheck";
35
+ private static final String KEY_APPCHECK_TOKEN_REFRESH_ENABLED = "app_check_token_auto_refresh";
36
+ ReactNativeFirebaseAppCheckProviderFactory providerFactory =
37
+ new ReactNativeFirebaseAppCheckProviderFactory();
38
+
39
+ static boolean isAppCheckTokenRefreshEnabled() {
40
+ boolean enabled;
41
+ ReactNativeFirebaseJSON json = ReactNativeFirebaseJSON.getSharedInstance();
42
+ ReactNativeFirebaseMeta meta = ReactNativeFirebaseMeta.getSharedInstance();
43
+ ReactNativeFirebasePreferences prefs = ReactNativeFirebasePreferences.getSharedInstance();
44
+
45
+ if (prefs.contains(KEY_APPCHECK_TOKEN_REFRESH_ENABLED)) {
46
+ enabled = prefs.getBooleanValue(KEY_APPCHECK_TOKEN_REFRESH_ENABLED, true);
47
+ Log.d(LOGTAG, "isAppCheckCollectionEnabled via RNFBPreferences: " + enabled);
48
+ } else if (json.contains(KEY_APPCHECK_TOKEN_REFRESH_ENABLED)) {
49
+ enabled = json.getBooleanValue(KEY_APPCHECK_TOKEN_REFRESH_ENABLED, true);
50
+ Log.d(LOGTAG, "isAppCheckCollectionEnabled via RNFBJSON: " + enabled);
51
+ } else {
52
+ enabled = meta.getBooleanValue(KEY_APPCHECK_TOKEN_REFRESH_ENABLED, true);
53
+ Log.d(LOGTAG, "isAppCheckCollectionEnabled via RNFBMeta: " + enabled);
54
+ }
55
+
56
+ if (BuildConfig.DEBUG) {
57
+ if (!json.getBooleanValue(KEY_APPCHECK_TOKEN_REFRESH_ENABLED, false)) {
58
+ enabled = false;
59
+ }
60
+ Log.d(
61
+ LOGTAG,
62
+ "isAppCheckTokenRefreshEnabled after checking "
63
+ + KEY_APPCHECK_TOKEN_REFRESH_ENABLED
64
+ + ": "
65
+ + enabled);
66
+ }
67
+
68
+ Log.d(LOGTAG, "isAppCheckTokenRefreshEnabled final value: " + enabled);
69
+ return enabled;
70
+ }
71
+
72
+ private boolean isAppDebuggable() throws Exception {
73
+ boolean isDebuggable = false;
74
+ PackageManager pm = getContext().getPackageManager();
75
+ if (pm != null) {
76
+ isDebuggable =
77
+ (0
78
+ != (pm.getApplicationInfo(getContext().getPackageName(), 0).flags
79
+ & ApplicationInfo.FLAG_DEBUGGABLE));
80
+ }
81
+ Log.d(LOGTAG, "debuggable status? " + isDebuggable);
82
+ return isDebuggable;
83
+ }
35
84
 
36
85
  ReactNativeFirebaseAppCheckModule(ReactApplicationContext reactContext) {
37
86
  super(reactContext, TAG);
87
+
88
+ // Our default token refresh config comes from config files, set it
89
+ FirebaseAppCheck firebaseAppCheck = FirebaseAppCheck.getInstance();
90
+ firebaseAppCheck.setTokenAutoRefreshEnabled(isAppCheckTokenRefreshEnabled());
91
+ }
92
+
93
+ @ReactMethod
94
+ public void configureProvider(
95
+ String appName, String providerName, String debugToken, Promise promise) {
96
+ Log.d(
97
+ LOGTAG,
98
+ "configureProvider - appName/providerName/debugToken: "
99
+ + appName
100
+ + "/"
101
+ + providerName
102
+ + (debugToken != null ? "/(not shown)" : "/null"));
103
+ try {
104
+ providerFactory.configure(appName, providerName, debugToken);
105
+ FirebaseAppCheck.getInstance(FirebaseApp.getInstance(appName))
106
+ .installAppCheckProviderFactory(providerFactory);
107
+ promise.resolve(null);
108
+ } catch (Exception e) {
109
+ rejectPromiseWithCodeAndMessage(promise, "unknown", "internal-error", e.getMessage());
110
+ }
38
111
  }
39
112
 
40
113
  @ReactMethod
41
114
  public void activate(
42
115
  String appName, String siteKeyProvider, boolean isTokenAutoRefreshEnabled, Promise promise) {
43
116
  try {
44
- FirebaseAppCheck firebaseAppCheck = FirebaseAppCheck.getInstance();
45
- firebaseAppCheck.setTokenAutoRefreshEnabled(isTokenAutoRefreshEnabled);
46
- boolean isDebuggable = false;
47
- PackageManager pm = getContext().getPackageManager();
48
- if (pm != null) {
49
- isDebuggable =
50
- (0
51
- != (pm.getApplicationInfo(getContext().getPackageName(), 0).flags
52
- & ApplicationInfo.FLAG_DEBUGGABLE));
53
- }
54
117
 
55
- if (isDebuggable) {
118
+ FirebaseAppCheck firebaseAppCheck =
119
+ FirebaseAppCheck.getInstance(FirebaseApp.getInstance(appName));
120
+ firebaseAppCheck.setTokenAutoRefreshEnabled(isTokenAutoRefreshEnabled);
56
121
 
122
+ // Configure our new proxy factory in a backwards-compatible way for old API
123
+ if (isAppDebuggable()) {
124
+ Log.d(LOGTAG, "app is debuggable, configuring AppCheck for testing mode");
57
125
  if (BuildConfig.FIREBASE_APP_CHECK_DEBUG_TOKEN != "null") {
58
- // Get DebugAppCheckProviderFactory class
59
- Class<DebugAppCheckProviderFactory> debugACFactoryClass =
60
- DebugAppCheckProviderFactory.class;
61
-
62
- // Get the (undocumented) constructor accepting a debug token as string
63
- Class<?>[] argType = {String.class};
64
- Constructor c = debugACFactoryClass.getDeclaredConstructor(argType);
65
-
66
- // Create a object containing the constructor arguments
67
- // and initialize a new instance.
68
- Object[] cArgs = {BuildConfig.FIREBASE_APP_CHECK_DEBUG_TOKEN};
69
- firebaseAppCheck.installAppCheckProviderFactory(
70
- (AppCheckProviderFactory) c.newInstance(cArgs));
126
+ Log.d(LOGTAG, "debug app check token found in BuildConfig. Installing known token.");
127
+ providerFactory.configure(appName, "debug", BuildConfig.FIREBASE_APP_CHECK_DEBUG_TOKEN);
71
128
  } else {
72
- firebaseAppCheck.installAppCheckProviderFactory(
73
- DebugAppCheckProviderFactory.getInstance());
129
+ Log.d(
130
+ LOGTAG,
131
+ "no debug app check token found in BuildConfig. Check Log for dynamic test token to"
132
+ + " configure in console.");
133
+ providerFactory.configure(appName, "debug", null);
74
134
  }
75
-
76
135
  } else {
77
- firebaseAppCheck.installAppCheckProviderFactory(
78
- SafetyNetAppCheckProviderFactory.getInstance());
136
+ providerFactory.configure(appName, "safetyNet", null);
79
137
  }
138
+
139
+ FirebaseAppCheck.getInstance(FirebaseApp.getInstance(appName))
140
+ .installAppCheckProviderFactory(providerFactory);
141
+ promise.resolve(null);
80
142
  } catch (Exception e) {
81
- rejectPromiseWithCodeAndMessage(promise, "unknown", "internal-error", "unimplemented");
82
- return;
143
+ rejectPromiseWithCodeAndMessage(promise, "unknown", "internal-error", e.getMessage());
83
144
  }
84
- promise.resolve(null);
85
145
  }
86
146
 
87
147
  @ReactMethod
@@ -92,6 +152,7 @@ public class ReactNativeFirebaseAppCheckModule extends ReactNativeFirebaseModule
92
152
 
93
153
  @ReactMethod
94
154
  public void getToken(String appName, boolean forceRefresh, Promise promise) {
155
+ Log.d(LOGTAG, "getToken appName/forceRefresh: " + appName + "/" + forceRefresh);
95
156
  FirebaseApp firebaseApp = FirebaseApp.getInstance(appName);
96
157
  Tasks.call(
97
158
  getExecutor(),
@@ -108,7 +169,7 @@ public class ReactNativeFirebaseAppCheckModule extends ReactNativeFirebaseModule
108
169
  promise.resolve(tokenResultMap);
109
170
  } else {
110
171
  Log.e(
111
- TAG,
172
+ LOGTAG,
112
173
  "RNFB: Unknown error while fetching AppCheck token "
113
174
  + task.getException().getMessage());
114
175
  rejectPromiseWithCodeAndMessage(
@@ -0,0 +1,85 @@
1
+ package io.invertase.firebase.appcheck;
2
+
3
+ /*
4
+ * Copyright (c) 2023-present Invertase Limited & Contributors
5
+ *
6
+ * Licensed under the Apache License, Version 2.0 (the "License");
7
+ * you may not use this library except in compliance with the License.
8
+ * You may obtain a copy of the License at
9
+ *
10
+ * http://www.apache.org/licenses/LICENSE-2.0
11
+ *
12
+ * Unless required by applicable law or agreed to in writing, software
13
+ * distributed under the License is distributed on an "AS IS" BASIS,
14
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ * See the License for the specific language governing permissions and
16
+ * limitations under the License.
17
+ *
18
+ */
19
+
20
+ import android.util.Log;
21
+ import com.google.android.gms.tasks.Task;
22
+ import com.google.firebase.FirebaseApp;
23
+ import com.google.firebase.appcheck.AppCheckProvider;
24
+ import com.google.firebase.appcheck.AppCheckProviderFactory;
25
+ import com.google.firebase.appcheck.AppCheckToken;
26
+ import com.google.firebase.appcheck.debug.DebugAppCheckProviderFactory;
27
+ import com.google.firebase.appcheck.playintegrity.PlayIntegrityAppCheckProviderFactory;
28
+ import com.google.firebase.appcheck.safetynet.SafetyNetAppCheckProviderFactory;
29
+ import java.lang.reflect.Constructor;
30
+
31
+ // Facade for all possible provider factory delegates,
32
+ // configurable dynamically instead of at startup
33
+ public class ReactNativeFirebaseAppCheckProvider implements AppCheckProvider {
34
+ private static final String LOGTAG = "RNFBAppCheck";
35
+
36
+ AppCheckProvider delegateProvider;
37
+
38
+ @Override
39
+ public Task<AppCheckToken> getToken() {
40
+ Log.d(LOGTAG, "Provider::getToken - delegating to native provider");
41
+ return delegateProvider.getToken();
42
+ }
43
+
44
+ public void configure(String appName, String providerName, String debugToken) {
45
+ Log.d(
46
+ LOGTAG,
47
+ "Provider::configure with appName/providerName/debugToken: "
48
+ + appName
49
+ + "/"
50
+ + providerName
51
+ + "/"
52
+ + (debugToken != null ? "(not shown)" : "null"));
53
+
54
+ try {
55
+ FirebaseApp app = FirebaseApp.getInstance(appName);
56
+
57
+ if ("debug".equals(providerName)) {
58
+
59
+ // the debug configuration may have a token, or may not
60
+ if (debugToken != null) {
61
+ // Create a debug provider using hidden factory constructor and our debug token
62
+ Class<DebugAppCheckProviderFactory> debugACFactoryClass =
63
+ DebugAppCheckProviderFactory.class;
64
+ Class<?>[] argType = {String.class};
65
+ Constructor c = debugACFactoryClass.getDeclaredConstructor(argType);
66
+ Object[] cArgs = {debugToken};
67
+ delegateProvider = ((AppCheckProviderFactory) c.newInstance(cArgs)).create(app);
68
+ } else {
69
+ delegateProvider = DebugAppCheckProviderFactory.getInstance().create(app);
70
+ }
71
+ }
72
+
73
+ if ("safetyNet".equals(providerName)) {
74
+ delegateProvider = SafetyNetAppCheckProviderFactory.getInstance().create(app);
75
+ }
76
+
77
+ if ("playIntegrity".equals(providerName)) {
78
+ delegateProvider = PlayIntegrityAppCheckProviderFactory.getInstance().create(app);
79
+ }
80
+ } catch (Exception e) {
81
+ // This will bubble up and result in a rejected promise with the underlying message
82
+ throw new RuntimeException(e.getMessage());
83
+ }
84
+ }
85
+ }
@@ -0,0 +1,66 @@
1
+ package io.invertase.firebase.appcheck;
2
+
3
+ /*
4
+ * Copyright (c) 2023-present Invertase Limited & Contributors
5
+ *
6
+ * Licensed under the Apache License, Version 2.0 (the "License");
7
+ * you may not use this library except in compliance with the License.
8
+ * You may obtain a copy of the License at
9
+ *
10
+ * http://www.apache.org/licenses/LICENSE-2.0
11
+ *
12
+ * Unless required by applicable law or agreed to in writing, software
13
+ * distributed under the License is distributed on an "AS IS" BASIS,
14
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ * See the License for the specific language governing permissions and
16
+ * limitations under the License.
17
+ *
18
+ */
19
+
20
+ import android.util.Log;
21
+ import com.google.firebase.FirebaseApp;
22
+ import com.google.firebase.appcheck.AppCheckProvider;
23
+ import com.google.firebase.appcheck.AppCheckProviderFactory;
24
+ import java.util.HashMap;
25
+ import java.util.Map;
26
+
27
+ public class ReactNativeFirebaseAppCheckProviderFactory implements AppCheckProviderFactory {
28
+ private static final String LOGTAG = "RNFBAppCheck";
29
+
30
+ // This object has one job - create + maintain control over one provider per app
31
+ public Map<String, ReactNativeFirebaseAppCheckProvider> providers = new HashMap();
32
+
33
+ // Our provider will serve as a facade to all the supported native providers,
34
+ // we will just pass through configuration calls to it
35
+ public void configure(String appName, String providerName, String debugToken) {
36
+ Log.d(
37
+ LOGTAG,
38
+ "ProviderFactory::configure - appName/providerName/debugToken: "
39
+ + appName
40
+ + "/"
41
+ + providerName
42
+ + (debugToken != null ? "/(not shown)" : "/null"));
43
+
44
+ ReactNativeFirebaseAppCheckProvider provider = null;
45
+
46
+ // Look up the correct provider for the given appName, create it if not created
47
+ provider = providers.get(appName);
48
+ if (provider == null) {
49
+ provider = new ReactNativeFirebaseAppCheckProvider();
50
+ providers.put(appName, provider);
51
+ }
52
+ provider.configure(appName, providerName, debugToken);
53
+ }
54
+
55
+ public AppCheckProvider create(FirebaseApp firebaseApp) {
56
+ String appName = firebaseApp.getName();
57
+ Log.d(LOGTAG, "ProviderFactory::create - fetching provider for app " + appName);
58
+ ReactNativeFirebaseAppCheckProvider provider = providers.get(appName);
59
+ if (provider == null) {
60
+ Log.d(LOGTAG, "ProviderFactory::create - provider not configured for this app.");
61
+ throw new RuntimeException(
62
+ "ReactNativeFirebaseAppCheckProvider not configured for app " + appName);
63
+ }
64
+ return provider;
65
+ }
66
+ }
@@ -0,0 +1,7 @@
1
+ // Subclass FIRAppCheckDebugProvider here
2
+ // https://developer.apple.com/library/archive/documentation/General/Conceptual/DevPedia-CocoaCore/MethodOverriding.html
3
+
4
+ // 1. have a new nullable configuredDebugToken property
5
+ // 2. override implementation of currentDebutToken to:
6
+ // - return configuredDebugToken if it exists,
7
+ // - else return [super currentDebugToken] if not
@@ -19,6 +19,12 @@
19
19
 
20
20
  #import <React/RCTBridgeModule.h>
21
21
 
22
+ #import "RNFBAppCheckProviderFactory.h"
23
+
22
24
  @interface RNFBAppCheckModule : NSObject <RCTBridgeModule>
23
25
 
26
+ @property RNFBAppCheckProviderFactory* _Nullable providerFactory;
27
+
28
+ + (_Nonnull instancetype)sharedInstance;
29
+
24
30
  @end
@@ -33,6 +33,17 @@ RCT_EXPORT_MODULE();
33
33
  return dispatch_get_main_queue();
34
34
  }
35
35
 
36
+ + (instancetype)sharedInstance {
37
+ static dispatch_once_t once;
38
+ __strong static RNFBAppCheckModule *sharedInstance;
39
+ dispatch_once(&once, ^{
40
+ sharedInstance = [[RNFBAppCheckModule alloc] init];
41
+ sharedInstance.providerFactory = [[RNFBAppCheckProviderFactory alloc] init];
42
+ [FIRAppCheck setAppCheckProviderFactory:sharedInstance.providerFactory];
43
+ });
44
+ return sharedInstance;
45
+ }
46
+
36
47
  #pragma mark -
37
48
  #pragma mark Firebase AppCheck Methods
38
49
 
@@ -42,21 +53,35 @@ RCT_EXPORT_METHOD(activate
42
53
  : (BOOL)isTokenAutoRefreshEnabled
43
54
  : (RCTPromiseResolveBlock)resolve rejecter
44
55
  : (RCTPromiseRejectBlock)reject) {
45
- // From SDK docs:
46
- // NOTE: Make sure to call this method before FirebaseApp.configure().
47
- // If this method is called after configuring Firebase, the changes will not take effect.
48
-
49
- // So in react-native-firebase we will only use this to set the isTokenAutoRefreshEnabled
50
- // parameter, but if AppCheck is included on iOS it wlil be active with DeviceCheckProviderFactory
56
+ DLog(@"deprecated API, provider will be deviceCheck / token refresh %d for app %@",
57
+ isTokenAutoRefreshEnabled, firebaseApp.name);
58
+ [[RNFBAppCheckModule sharedInstance].providerFactory configure:firebaseApp
59
+ providerName:@"deviceCheck"
60
+ debugToken:nil];
51
61
 
52
62
  FIRAppCheck *appCheck = [FIRAppCheck appCheckWithApp:firebaseApp];
53
63
  appCheck.isTokenAutoRefreshEnabled = isTokenAutoRefreshEnabled;
54
64
  resolve([NSNull null]);
55
65
  }
56
66
 
67
+ RCT_EXPORT_METHOD(configureProvider
68
+ : (FIRApp *)firebaseApp
69
+ : (nonnull NSString *)providerName
70
+ : (nullable NSString *)debugToken
71
+ : (RCTPromiseResolveBlock)resolve rejecter
72
+ : (RCTPromiseRejectBlock)reject) {
73
+ DLog(@"appName/providerName/debugToken: %@/%@/%@", firebaseApp.name, providerName,
74
+ (debugToken == nil ? @"null" : @"(not shown)"));
75
+ [[RNFBAppCheckModule sharedInstance].providerFactory configure:firebaseApp
76
+ providerName:providerName
77
+ debugToken:debugToken];
78
+ resolve([NSNull null]);
79
+ }
80
+
57
81
  RCT_EXPORT_METHOD(setTokenAutoRefreshEnabled
58
82
  : (FIRApp *)firebaseApp
59
83
  : (BOOL)isTokenAutoRefreshEnabled) {
84
+ DLog(@"app/isTokenAutoRefreshEnabled: %@/%d", firebaseApp.name, isTokenAutoRefreshEnabled);
60
85
  FIRAppCheck *appCheck = [FIRAppCheck appCheckWithApp:firebaseApp];
61
86
  appCheck.isTokenAutoRefreshEnabled = isTokenAutoRefreshEnabled;
62
87
  }
@@ -69,6 +94,7 @@ RCT_EXPORT_METHOD(isTokenAutoRefreshEnabled
69
94
  : (RCTPromiseRejectBlock)reject) {
70
95
  FIRAppCheck *appCheck = [FIRAppCheck appCheckWithApp:firebaseApp];
71
96
  BOOL isTokenAutoRefreshEnabled = appCheck.isTokenAutoRefreshEnabled;
97
+ DLog(@"app/isTokenAutoRefreshEnabled: %@/%d", firebaseApp.name, isTokenAutoRefreshEnabled);
72
98
  resolve([NSNumber numberWithBool:isTokenAutoRefreshEnabled]);
73
99
  }
74
100
 
@@ -78,41 +104,34 @@ RCT_EXPORT_METHOD(getToken
78
104
  : (RCTPromiseResolveBlock)resolve
79
105
  : (RCTPromiseRejectBlock)reject) {
80
106
  FIRAppCheck *appCheck = [FIRAppCheck appCheckWithApp:firebaseApp];
81
- [appCheck tokenForcingRefresh:forceRefresh
82
- completion:^(FIRAppCheckToken *_Nullable token, NSError *_Nullable error) {
83
- if (error != nil) {
84
- // Handle any errors if the token was not retrieved.
85
- DLog(@"Unable to retrieve App Check token: %@", error);
86
- [RNFBSharedUtils
87
- rejectPromiseWithUserInfo:reject
88
- userInfo:(NSMutableDictionary *)@{
89
- @"code" : @"token-error",
90
- @"message" : [error localizedDescription],
91
- }];
92
- return;
93
- }
94
- if (token == nil) {
95
- DLog(@"Unable to retrieve App Check token.");
96
- [RNFBSharedUtils rejectPromiseWithUserInfo:reject
97
- userInfo:(NSMutableDictionary *)@{
98
- @"code" : @"token-null",
99
- @"message" : @"no token fetched",
100
- }];
101
- return;
102
- }
103
-
104
- NSMutableDictionary *tokenResultDictionary = [NSMutableDictionary new];
105
- tokenResultDictionary[@"token"] = token.token;
106
- resolve(tokenResultDictionary);
107
- }];
107
+ DLog(@"appName %@", firebaseApp.name);
108
+ [appCheck
109
+ tokenForcingRefresh:forceRefresh
110
+ completion:^(FIRAppCheckToken *_Nullable token, NSError *_Nullable error) {
111
+ if (error != nil) {
112
+ // Handle any errors if the token was not retrieved.
113
+ DLog(@"RNFBAppCheck - getToken - Unable to retrieve App Check token: %@", error);
114
+ [RNFBSharedUtils rejectPromiseWithUserInfo:reject
115
+ userInfo:(NSMutableDictionary *)@{
116
+ @"code" : @"token-error",
117
+ @"message" : [error localizedDescription],
118
+ }];
119
+ return;
120
+ }
121
+ if (token == nil) {
122
+ DLog(@"RNFBAppCheck - getToken - Unable to retrieve App Check token.");
123
+ [RNFBSharedUtils rejectPromiseWithUserInfo:reject
124
+ userInfo:(NSMutableDictionary *)@{
125
+ @"code" : @"token-null",
126
+ @"message" : @"no token fetched",
127
+ }];
128
+ return;
129
+ }
130
+
131
+ NSMutableDictionary *tokenResultDictionary = [NSMutableDictionary new];
132
+ tokenResultDictionary[@"token"] = token.token;
133
+ resolve(tokenResultDictionary);
134
+ }];
108
135
  }
109
136
 
110
- // TODO
111
- // - set up DeviceCheckProvider and Debug provider
112
- // FIRAppCheckDebugProviderFactory *providerFactory =
113
- // [[FIRAppCheckDebugProviderFactory alloc] init];
114
- // [FIRAppCheck setAppCheckProviderFactory:providerFactory];
115
-
116
- // Write a custom provider factory, and allow the AppAttest provider
117
-
118
137
  @end
@@ -0,0 +1,33 @@
1
+ /**
2
+ * Copyright (c) 2023-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 <Firebase/Firebase.h>
19
+ #import <FirebaseAppCheck/FIRAppCheck.h>
20
+
21
+ @interface RNFBAppCheckProvider : NSObject <FIRAppCheckProvider>
22
+
23
+ @property FIRApp *app;
24
+
25
+ @property id<FIRAppCheckProvider> delegateProvider;
26
+
27
+ - (void)configure:(FIRApp *)app
28
+ providerName:(NSString *)providerName
29
+ debugToken:(NSString *)debugToken;
30
+
31
+ - (id)initWithApp:(FIRApp *)app;
32
+
33
+ @end
@@ -0,0 +1,87 @@
1
+ /**
2
+ * Copyright (c) 2023-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 "RNFBAppCheckProvider.h"
18
+ #import "RNFBApp/RNFBSharedUtils.h"
19
+
20
+ @implementation RNFBAppCheckProvider
21
+
22
+ - (id)initWithApp:app {
23
+ DLog(@"RNFBAppCheck init with app %@", [app name]);
24
+ self = [super init];
25
+ if (self) {
26
+ self.app = app;
27
+ }
28
+ return self;
29
+ }
30
+
31
+ - (void)configure:(FIRApp *)app
32
+ providerName:(NSString *)providerName
33
+ debugToken:(NSString *)debugToken {
34
+ DLog(@"appName/providerName/debugToken: %@/%@/%@", app.name, providerName,
35
+ (debugToken == nil ? @"null" : @"(not shown)"));
36
+
37
+ DLog(@"appName %@", app.name);
38
+
39
+ // - determine if debugToken is provided via nullable arg
40
+ if ([providerName isEqualToString:@"debug"]) {
41
+ // TODO: Currently not handling debugToken argument, relying on existing environment
42
+ // variable configuration style.
43
+ // - maybe directly setting an environment variable could work?
44
+ // https://stackoverflow.com/questions/27139589/whats-the-idiomatic-way-of-setting-an-environment-variable-in-objective-c-coco
45
+ // - ...otherwise if env var does not work
46
+ // - subclass style: RNFBAppCheckDebugProvider, and we should print local token
47
+ // - if a debugToken parameter was supplied, set
48
+ // RNFBAppCheckDebugProvider.configuredDebugToken
49
+ // - print local token
50
+ // https://github.com/firebase/firebase-ios-sdk/blob/c7e95996ff/FirebaseAppCheck/Sources/DebugProvider/FIRAppCheckDebugProviderFactory.m
51
+ // - print if current token in provided by configuration, by environment variable, or local
52
+ // token?
53
+
54
+ self.delegateProvider = [[FIRAppCheckDebugProvider new] initWithApp:app];
55
+ }
56
+
57
+ if ([providerName isEqualToString:@"deviceCheck"]) {
58
+ self.delegateProvider = [[FIRDeviceCheckProvider new] initWithApp:app];
59
+ }
60
+
61
+ if ([providerName isEqualToString:@"appAttest"]) {
62
+ if (@available(iOS 14.0, macCatalyst 14.0, tvOS 15.0, watchOS 9.0, *)) {
63
+ self.delegateProvider = [[FIRAppAttestProvider alloc] initWithApp:app];
64
+ } else {
65
+ // This is not a valid configuration.
66
+ DLog(@"AppAttest unavailable: it requires iOS14+, macCatalyst14+ or tvOS15+. Installing "
67
+ @"debug provider to guarantee invalid tokens in this invalid configuration.");
68
+ self.delegateProvider = [[FIRAppCheckDebugProvider new] initWithApp:app];
69
+ }
70
+ }
71
+
72
+ if ([providerName isEqualToString:@"appAttestWithDeviceCheckFallback"]) {
73
+ if (@available(iOS 14.0, *)) {
74
+ self.delegateProvider = [[FIRAppAttestProvider alloc] initWithApp:app];
75
+ } else {
76
+ self.delegateProvider = [[FIRDeviceCheckProvider alloc] initWithApp:app];
77
+ }
78
+ }
79
+ }
80
+
81
+ - (void)getTokenWithCompletion:(nonnull void (^)(FIRAppCheckToken *_Nullable,
82
+ NSError *_Nullable))handler {
83
+ DLog(@"proxying to delegateProvider...");
84
+ [self.delegateProvider getTokenWithCompletion:handler];
85
+ }
86
+
87
+ @end
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Copyright (c) 2023-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 <FirebaseAppCheck/FIRAppCheck.h>
19
+
20
+ @interface RNFBAppCheckProviderFactory : NSObject <FIRAppCheckProviderFactory>
21
+
22
+ @property NSMutableDictionary *_Nullable providers;
23
+
24
+ - (void)configure:(FIRApp *_Nonnull)app
25
+ providerName:(NSString *_Nonnull)providerName
26
+ debugToken:(NSString *_Nullable)debugToken;
27
+
28
+ @end
@@ -0,0 +1,64 @@
1
+ /**
2
+ * Copyright (c) 2023-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 "RNFBAppCheckProviderFactory.h"
19
+ #import <Firebase/Firebase.h>
20
+ #import <FirebaseAppCheck/FIRAppCheck.h>
21
+ #import "RNFBApp/RNFBSharedUtils.h"
22
+ #import "RNFBAppCheckProvider.h"
23
+
24
+ @implementation RNFBAppCheckProviderFactory
25
+
26
+ - (nullable id<FIRAppCheckProvider>)createProviderWithApp:(FIRApp *)app {
27
+ DLog(@"appName %@", app.name);
28
+
29
+ // The SDK may try to call this before we have been configured,
30
+ // so we will configure ourselves and set the provider up as a default to start
31
+ // pre-configure
32
+ if (self.providers == nil) {
33
+ DLog(@"providers dictionary initializing for app %@", app.name);
34
+ self.providers = [NSMutableDictionary new];
35
+ }
36
+
37
+ if (self.providers[app.name] == nil) {
38
+ DLog(@"provider initializing (with default to debug) for app %@", app.name);
39
+ self.providers[app.name] = [RNFBAppCheckProvider new];
40
+ RNFBAppCheckProvider *provider = self.providers[app.name];
41
+ [provider configure:app providerName:@"debug" debugToken:nil];
42
+ }
43
+
44
+ return self.providers[app.name];
45
+ }
46
+
47
+ - (void)configure:(FIRApp *)app
48
+ providerName:(NSString *)providerName
49
+ debugToken:(NSString *)debugToken {
50
+ DLog(@"appName/providerName/debugToken: %@/%@/%@", app.name, providerName,
51
+ (debugToken == nil ? @"null" : @"(not shown)"));
52
+ if (self.providers == nil) {
53
+ self.providers = [NSMutableDictionary new];
54
+ }
55
+
56
+ if (self.providers[app.name] == nil) {
57
+ self.providers[app.name] = [RNFBAppCheckProvider new];
58
+ }
59
+
60
+ RNFBAppCheckProvider *provider = self.providers[app.name];
61
+ [provider configure:app providerName:providerName debugToken:debugToken];
62
+ }
63
+
64
+ @end
@@ -0,0 +1,9 @@
1
+ export default class ReactNativeFirebaseAppCheckProvider {
2
+ providerOptions;
3
+
4
+ constructor() {}
5
+
6
+ configure(options) {
7
+ this.providerOptions = options;
8
+ }
9
+ }
package/lib/index.d.ts CHANGED
@@ -68,6 +68,81 @@ export namespace FirebaseAppCheckTypes {
68
68
  getToken(): Promise<AppCheckToken>;
69
69
  }
70
70
 
71
+ /**
72
+ * Options for App Check initialization.
73
+ */
74
+ export interface AppCheckOptions {
75
+ /**
76
+ * A reCAPTCHA V3 provider, reCAPTCHA Enterprise provider, or custom provider.
77
+ * Note that in react-native-firebase provider should always be ReactNativeAppCheckCustomProvider, a cross-platform
78
+ * implementation of an AppCheck CustomProvider
79
+ */
80
+ provider: CustomProvider | ReCaptchaV3Provider | ReCaptchaEnterpriseProvider;
81
+
82
+ /**
83
+ * If true, enables SDK to automatically
84
+ * refresh AppCheck token as needed. If undefined, the value will default
85
+ * to the value of `app.automaticDataCollectionEnabled`. That property
86
+ * defaults to false and can be set in the app config.
87
+ */
88
+ isTokenAutoRefreshEnabled?: boolean;
89
+ }
90
+
91
+ export interface ReactNativeFirebaseAppCheckProviderOptions {
92
+ /**
93
+ * debug token to use, if any. Defaults to undefined, pre-configure tokens in firebase web console if needed
94
+ */
95
+ debugToken?: string;
96
+ }
97
+
98
+ export interface ReactNativeFirebaseAppCheckProviderWebOptions
99
+ extends ReactNativeFirebaseAppCheckProviderOptions {
100
+ /**
101
+ * The web provider to use, either `reCaptchaV3` or `reCaptchaEnterprise`, defaults to `reCaptchaV3`
102
+ */
103
+ provider?: 'debug' | 'reCaptchaV3' | 'reCaptchaEnterprise';
104
+
105
+ /**
106
+ * siteKey for use in web queries, defaults to `none`
107
+ */
108
+ siteKey?: string;
109
+ }
110
+
111
+ export interface ReactNativeFirebaseAppCheckProviderAppleOptions
112
+ extends ReactNativeFirebaseAppCheckProviderOptions {
113
+ /**
114
+ * The apple provider to use, either `deviceCheck` or `appAttest`, or `appAttestWithDeviceCheckFallback`,
115
+ * defaults to `DeviceCheck`. `appAttest` requires iOS 14+ or will fail, `appAttestWithDeviceCheckFallback`
116
+ * will use `appAttest` for iOS14+ and fallback to `deviceCheck` on devices with ios13 and lower
117
+ */
118
+ provider?: 'debug' | 'deviceCheck' | 'appAttest' | 'appAttestWithDeviceCheckFallback';
119
+ }
120
+
121
+ export interface ReactNativeFirebaseAppCheckProviderAndroidOptions
122
+ extends ReactNativeFirebaseAppCheckProviderOptions {
123
+ /**
124
+ * The android provider to use, either `safetyNet` or `playIntegrity`. default is `playIntegrity`, `safetyNet` is deprecated.
125
+ */
126
+ provider?: 'debug' | 'safetyNet' | 'playIntegrity';
127
+ }
128
+
129
+ export interface ReactNativeFirebaseAppCheckProvider extends AppCheckProvider {
130
+ /**
131
+ * Specify how the app check provider should be configured. The new configuration is
132
+ * in effect when this call returns. You must call `getToken()`
133
+ * after this call to get a token using the new configuration.
134
+ * This custom provider allows for delayed configuration and re-configuration on all platforms
135
+ * so AppCheck has the same experience across all platforms, with the only difference being the native
136
+ * providers you choose to use on each platform.
137
+ */
138
+ configure(
139
+ web?: ReactNativeFirebaseAppCheckProviderWebOptions,
140
+ android?: ReactNativeFirebaseAppCheckProviderAndroidOptions,
141
+ apple?: ReactNativeFirebaseAppCheckProviderAppleOptions,
142
+ isTokenAutoRefreshEnabled?: boolean,
143
+ ): Promise<void>;
144
+ }
145
+
71
146
  /**
72
147
  * Result returned by `getToken()`.
73
148
  */
@@ -118,6 +193,16 @@ export namespace FirebaseAppCheckTypes {
118
193
  *
119
194
  */
120
195
  export class Module extends FirebaseModule {
196
+ /**
197
+ * initialize the AppCheck module. Note that in react-native-firebase AppCheckOptions must always
198
+ * be an object with a `provider` member containing `ReactNativeFirebaseAppCheckProvider` that has returned successfully
199
+ * from a call to the `configure` method, with sub-providers for the various platforms configured to meet your project
200
+ * requirements. This must be called prior to interacting with any firebase services protected by AppCheck
201
+ *
202
+ * @param options an AppCheckOptions with a configured ReactNativeFirebaseAppCheckProvider as the provider
203
+ */
204
+ initializeAppCheck(options: AppCheckOptions): Promise<void>;
205
+
121
206
  /**
122
207
  * Activate App Check
123
208
  * 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 {
127
212
  * On iOS if you want to set a specific AppCheckProviderFactory (for instance to FIRAppCheckDebugProviderFactory or
128
213
  * FIRAppAttestProvider) you must manually do that in your AppDelegate.m prior to calling [FIRApp configure]
129
214
  *
215
+ * @deprecated use initializeAppCheck to gain access to all platform providers and firebase-js-sdk v9 compatibility
130
216
  * @param siteKeyOrProvider - This is ignored, Android uses DebugProviderFactory if the app is debuggable (https://firebase.google.com/docs/app-check/android/debug-provider)
131
217
  * Android uses SafetyNetProviderFactory for release builds.
132
218
  * iOS uses DeviceCheckProviderFactory by default unless altered in AppDelegate.m manually
package/lib/index.js CHANGED
@@ -21,6 +21,8 @@ import {
21
21
  FirebaseModule,
22
22
  getFirebaseRoot,
23
23
  } from '@react-native-firebase/app/lib/internal';
24
+ import { Platform } from 'react-native';
25
+ import ReactNativeFirebaseAppCheckProvider from './ReactNativeFirebaseAppCheckProvider';
24
26
 
25
27
  import version from './version';
26
28
 
@@ -31,27 +33,70 @@ const namespace = 'appCheck';
31
33
  const nativeModuleName = 'RNFBAppCheckModule';
32
34
 
33
35
  class FirebaseAppCheckModule extends FirebaseModule {
34
- activate(siteKeyOrProvider, isTokenAutoRefreshEnabled) {
35
- if (!isString(siteKeyOrProvider)) {
36
- throw new Error('siteKeyOrProvider must be a string value to match firebase-js-sdk API');
37
- }
36
+ getIsTokenRefreshEnabledDefault() {
37
+ // no default to start
38
+ isTokenAutoRefreshEnabled = undefined;
38
39
 
39
- // If the caller did not specify token refresh, attempt to use app-check specific setting:
40
- if (!isBoolean(isTokenAutoRefreshEnabled)) {
41
- isTokenAutoRefreshEnabled = this.firebaseJson.app_check_token_auto_refresh;
40
+ return isTokenAutoRefreshEnabled;
41
+ }
42
+
43
+ newReactNativeFirebaseAppCheckProvider() {
44
+ return new ReactNativeFirebaseAppCheckProvider();
45
+ }
46
+
47
+ initializeAppCheck(options) {
48
+ // determine token refresh setting, if not specified
49
+ if (!isBoolean(options.isTokenAutoRefreshEnabled)) {
50
+ options.isTokenAutoRefreshEnabled = this.firebaseJson.app_check_token_auto_refresh;
42
51
  }
43
52
 
44
53
  // If that was not defined, attempt to use app-wide data collection setting per docs:
45
- if (!isBoolean(isTokenAutoRefreshEnabled)) {
46
- isTokenAutoRefreshEnabled = this.firebaseJson.app_data_collection_default_enabled;
54
+ if (!isBoolean(options.isTokenAutoRefreshEnabled)) {
55
+ options.isTokenAutoRefreshEnabled = this.firebaseJson.app_data_collection_default_enabled;
47
56
  }
48
57
 
49
58
  // If that also was not defined, the default is documented as true.
50
- if (!isBoolean(isTokenAutoRefreshEnabled)) {
51
- isTokenAutoRefreshEnabled = true;
59
+ if (!isBoolean(options.isTokenAutoRefreshEnabled)) {
60
+ options.isTokenAutoRefreshEnabled = true;
61
+ }
62
+ this.native.setTokenAutoRefreshEnabled(options.isTokenAutoRefreshEnabled);
63
+
64
+ if (Platform.OS === 'android') {
65
+ return this.native.configureProvider(
66
+ options.provider.providerOptions.android.provider,
67
+ options.provider.providerOptions.android.debugToken,
68
+ );
69
+ }
70
+ if (Platform.OS === 'ios' || Platform.OS === 'macos') {
71
+ return this.native.configureProvider(
72
+ options.provider.providerOptions.apple.provider,
73
+ options.provider.providerOptions.apple.debugToken,
74
+ );
75
+ }
76
+ throw new Error('Unsupported platform: ' + Platform.OS);
77
+ }
78
+
79
+ activate(siteKeyOrProvider, isTokenAutoRefreshEnabled) {
80
+ if (!isString(siteKeyOrProvider)) {
81
+ throw new Error('siteKeyOrProvider must be a string value to match firebase-js-sdk API');
52
82
  }
53
83
 
54
- return this.native.activate(siteKeyOrProvider, isTokenAutoRefreshEnabled);
84
+ // We wrap our new flexible interface, with compatible defaults
85
+ rnfbProvider = new ReactNativeFirebaseAppCheckProvider();
86
+ rnfbProvider.configure({
87
+ android: {
88
+ provider: 'safetyNet',
89
+ },
90
+ apple: {
91
+ provider: 'deviceCheck',
92
+ },
93
+ web: {
94
+ provider: 'reCaptchaV3',
95
+ siteKey: 'none',
96
+ },
97
+ });
98
+
99
+ return this.initializeAppCheck({ provider: rnfbProvider, isTokenAutoRefreshEnabled });
55
100
  }
56
101
 
57
102
  setTokenAutoRefreshEnabled(isTokenAutoRefreshEnabled) {
package/lib/version.js CHANGED
@@ -1,2 +1,2 @@
1
1
  // Generated by genversion.
2
- module.exports = '16.6.0';
2
+ module.exports = '17.0.0';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@react-native-firebase/app-check",
3
- "version": "16.6.0",
3
+ "version": "17.0.0",
4
4
  "author": "Invertase <oss@invertase.io> (http://invertase.io)",
5
5
  "description": "React Native Firebase - App Check",
6
6
  "main": "lib/index.js",
@@ -23,10 +23,10 @@
23
23
  "appCheck"
24
24
  ],
25
25
  "peerDependencies": {
26
- "@react-native-firebase/app": "16.6.0"
26
+ "@react-native-firebase/app": "17.0.0"
27
27
  },
28
28
  "publishConfig": {
29
29
  "access": "public"
30
30
  },
31
- "gitHead": "6663dc24dc5697190b930aa510085a681fe81203"
31
+ "gitHead": "53a436278f3c4cb23e1ddb7d2c9f14a65ce4abc1"
32
32
  }