diff --git a/packages/app-check/__tests__/appcheck.test.ts b/packages/app-check/__tests__/appcheck.test.ts index 581cc123c4..496fa6a513 100644 --- a/packages/app-check/__tests__/appcheck.test.ts +++ b/packages/app-check/__tests__/appcheck.test.ts @@ -1,4 +1,5 @@ -import { describe, expect, it } from '@jest/globals'; +import { describe, expect, it, beforeEach, afterEach } from '@jest/globals'; +import { Platform } from 'react-native'; import { initializeAppCheck, @@ -26,6 +27,74 @@ describe('appCheck()', function () { ); }); + describe('provider name validation', function () { + it('throws on invalid android provider name', function () { + const provider = new ReactNativeFirebaseAppCheckProvider(); + provider.configure({ + android: { provider: 'invalidProvider' as any }, + }); + expect(() => initializeAppCheck(undefined, { provider })).toThrow( + 'Invalid App Check provider "invalidProvider". Valid android providers are: debug, playIntegrity.', + ); + }); + + it('does not throw validation error for valid android provider names', function () { + for (const name of ['debug', 'playIntegrity']) { + const provider = new ReactNativeFirebaseAppCheckProvider(); + provider.configure({ + android: { provider: name as any }, + }); + try { + initializeAppCheck(undefined, { provider }); + } catch (e: any) { + expect(e.message).not.toContain('Invalid App Check provider'); + } + } + }); + + describe('apple platform', function () { + let originalOS: string; + + beforeEach(function () { + originalOS = Platform.OS; + Platform.OS = 'ios' as any; + }); + + afterEach(function () { + Platform.OS = originalOS as any; + }); + + it('throws on invalid apple provider name', function () { + const provider = new ReactNativeFirebaseAppCheckProvider(); + provider.configure({ + apple: { provider: 'appAttestWithDebugProviderFallback' as any }, + }); + expect(() => initializeAppCheck(undefined, { provider })).toThrow( + 'Invalid App Check provider "appAttestWithDebugProviderFallback". Valid apple providers are: debug, deviceCheck, appAttest, appAttestWithDeviceCheckFallback.', + ); + }); + + it('does not throw validation error for valid apple provider names', function () { + for (const name of [ + 'debug', + 'deviceCheck', + 'appAttest', + 'appAttestWithDeviceCheckFallback', + ]) { + const provider = new ReactNativeFirebaseAppCheckProvider(); + provider.configure({ + apple: { provider: name as any }, + }); + try { + initializeAppCheck(undefined, { provider }); + } catch (e: any) { + expect(e.message).not.toContain('Invalid App Check provider'); + } + } + }); + }); + }); + it('`getToken` function is properly exposed to end user', function () { expect(getToken).toBeDefined(); }); 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 index 730b1ad0aa..f56d6b628a 100644 --- 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 @@ -89,6 +89,15 @@ public String getDebugSecret() { if ("playIntegrity".equals(providerName)) { delegateProvider = PlayIntegrityAppCheckProviderFactory.getInstance().create(app); } + + if (delegateProvider == null) { + String message = + "Unknown provider name \"" + + providerName + + "\". Valid providers are: debug, playIntegrity."; + Log.e(LOGTAG, message); + throw new IllegalArgumentException(message); + } } 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/ios/RNFBAppCheck/RNFBAppCheckModule.mm b/packages/app-check/ios/RNFBAppCheck/RNFBAppCheckModule.mm index f3027d9fe9..6d8bcbe890 100644 --- a/packages/app-check/ios/RNFBAppCheck/RNFBAppCheckModule.mm +++ b/packages/app-check/ios/RNFBAppCheck/RNFBAppCheckModule.mm @@ -76,10 +76,18 @@ - (void)configureProvider:(NSString *)appName FIRApp *firebaseApp = [RCTConvert firAppFromString:appName]; DLog(@"appName/providerName/debugToken: %@/%@/%@", firebaseApp.name, providerName, (debugToken == nil ? @"null" : @"(not shown)")); - [[RNFBAppCheckModule sharedInstance].providerFactory configure:firebaseApp - providerName:providerName - debugToken:debugToken]; - resolve([NSNull null]); + @try { + [[RNFBAppCheckModule sharedInstance].providerFactory configure:firebaseApp + providerName:providerName + debugToken:debugToken]; + resolve([NSNull null]); + } @catch (NSException *exception) { + [RNFBSharedUtils rejectPromiseWithUserInfo:reject + userInfo:(NSMutableDictionary *)@{ + @"code" : @"unknown", + @"message" : exception.reason ?: @"internal-error", + }]; + } } - (void)setTokenAutoRefreshEnabled:(NSString *)appName diff --git a/packages/app-check/ios/RNFBAppCheck/RNFBAppCheckProvider.m b/packages/app-check/ios/RNFBAppCheck/RNFBAppCheckProvider.m index 07c3ace612..12a24541fa 100644 --- a/packages/app-check/ios/RNFBAppCheck/RNFBAppCheckProvider.m +++ b/packages/app-check/ios/RNFBAppCheck/RNFBAppCheckProvider.m @@ -36,7 +36,8 @@ - (void)configure:(FIRApp *)app DLog(@"appName %@", app.name); - // - determine if debugToken is provided via nullable arg + self.delegateProvider = nil; + if ([providerName isEqualToString:@"debug"]) { // The firebase-ios-sdk debug app check provider will take a token from environment if it // exists: @@ -73,6 +74,15 @@ - (void)configure:(FIRApp *)app self.delegateProvider = [[FIRDeviceCheckProvider alloc] initWithApp:app]; } } + + if (self.delegateProvider == nil) { + NSString *message = + [NSString stringWithFormat:@"Unknown provider name \"%@\". Valid providers are: debug, " + "deviceCheck, appAttest, appAttestWithDeviceCheckFallback.", + providerName ?: @"(null)"]; + NSLog(@"RNFBAppCheck: %@", message); + @throw [NSException exceptionWithName:@"RNFBAppCheckException" reason:message userInfo:nil]; + } } - (void)getTokenWithCompletion:(nonnull void (^)(FIRAppCheckToken *_Nullable, diff --git a/packages/app-check/lib/index.ts b/packages/app-check/lib/index.ts index 6a5d8d1517..a96de4f1fe 100644 --- a/packages/app-check/lib/index.ts +++ b/packages/app-check/lib/index.ts @@ -49,6 +49,14 @@ import { ReactNativeFirebaseAppCheckProvider } from './providers'; const nativeModuleName = 'NativeRNFBTurboAppCheck'; +const VALID_APPLE_PROVIDERS = [ + 'debug', + 'deviceCheck', + 'appAttest', + 'appAttestWithDeviceCheckFallback', +]; +const VALID_ANDROID_PROVIDERS = ['debug', 'playIntegrity']; + /** * Type guard to check if a provider has providerOptions. * This provides proper type narrowing for providers that support platform-specific configuration. @@ -63,6 +71,29 @@ function hasProviderOptions( ); } +function validateProviderName(options: AppCheckOptions): void { + if (!hasProviderOptions(options.provider)) { + return; + } + const provider = options.provider; + if (Platform.OS === 'android') { + const name = provider.providerOptions?.android?.provider; + if (isString(name) && !VALID_ANDROID_PROVIDERS.includes(name)) { + throw new Error( + `Invalid App Check provider "${name}". Valid android providers are: ${VALID_ANDROID_PROVIDERS.join(', ')}.`, + ); + } + } + if (Platform.OS === 'ios' || Platform.OS === 'macos') { + const name = provider.providerOptions?.apple?.provider; + if (isString(name) && !VALID_APPLE_PROVIDERS.includes(name)) { + throw new Error( + `Invalid App Check provider "${name}". Valid apple providers are: ${VALID_APPLE_PROVIDERS.join(', ')}.`, + ); + } + } +} + class FirebaseAppCheckModule extends FirebaseModule { _listenerCount: number; @@ -267,6 +298,7 @@ export function initializeAppCheck(app?: FirebaseApp, options?: AppCheckOptions) if (!isObject(options)) { throw new Error('Invalid configuration: no options defined.'); } + validateProviderName(options); const appCheck = getModularAppCheck(app); void (appCheck as AppCheckInternal).initializeAppCheck(options); return appCheck;