Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
71 changes: 70 additions & 1 deletion packages/app-check/__tests__/appcheck.test.ts
Original file line number Diff line number Diff line change
@@ -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,
Expand Down Expand Up @@ -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');
}
Comment thread
just1and0 marked this conversation as resolved.
}
});

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');
}
Comment thread
just1and0 marked this conversation as resolved.
}
});
});
});

it('`getToken` function is properly exposed to end user', function () {
expect(getToken).toBeDefined();
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Comment thread
just1and0 marked this conversation as resolved.
} catch (Exception e) {
// This will bubble up and result in a rejected promise with the underlying message
throw new RuntimeException(e.getMessage());
Expand Down
6 changes: 6 additions & 0 deletions packages/app-check/ios/RNFBAppCheck/RNFBAppCheckProvider.m
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,12 @@ - (void)configure:(FIRApp *)app
self.delegateProvider = [[FIRDeviceCheckProvider alloc] initWithApp:app];
}
}

if (self.delegateProvider == nil) {
NSLog(@"RNFBAppCheck: Unknown provider name \"%@\". Valid providers are: debug, deviceCheck, "
@"appAttest, appAttestWithDeviceCheckFallback.",
providerName);
}
Comment on lines +77 to +81

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same concern as the Android nil/delegateProvider == null path that was addressed with IllegalArgumentException: this currently only logs, then configureProvider still resolves successfully. Later [self.delegateProvider getTokenWithCompletion:] on nil is a silent ObjC no-op (completion never fires), rather than a clear init failure.

Also worth resetting self.delegateProvider = nil at the start of configure: — otherwise a prior factory default of "debug" can leave a live delegate after an unknown reconfigure, and this nil check never fires.

Proposed parity with Android (touches provider + module):

RNFBAppCheckProvider.m:

- (void)configure:(FIRApp *)app
     providerName:(NSString *)providerName
       debugToken:(NSString *)debugToken {
  // ... existing logging ...

  // Each configure replaces the prior delegate (same as Android).
  self.delegateProvider = nil;

  // ... existing providerName if-branches unchanged ...

  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];
  }
}

RNFBAppCheckModule.m (configureProvider) — catch and reject like Android's try/catch:

@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)getTokenWithCompletion:(nonnull void (^)(FIRAppCheckToken *_Nullable,
Expand Down
32 changes: 32 additions & 0 deletions packages/app-check/lib/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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<typeof nativeModuleName> {
_listenerCount: number;

Expand Down Expand Up @@ -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;
Expand Down
Loading