Skip to content

Commit 06dcae4

Browse files
committed
fix: validate listenerOrObserver callbacks in auth, app-check, remote-config
previously these sorts of callback reservations were not validated to make sure that they had a valid callback or observer, they were just blindly used this would lead to native perhaps getting events, emitting them to javascript, and javascript attempting to blindly execute an undefined function, then crashing
1 parent 959c4cf commit 06dcae4

File tree

5 files changed

+42
-26
lines changed

5 files changed

+42
-26
lines changed

packages/app-check/lib/index.js

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import {
2323
isFunction,
2424
isUndefined,
2525
isOther,
26+
parseListenerOrObserver,
2627
} from '@react-native-firebase/app/lib/common';
2728
import {
2829
createModuleNamespace,
@@ -179,12 +180,6 @@ class FirebaseAppCheckModule extends FirebaseModule {
179180
return this.native.getLimitedUseToken();
180181
}
181182

182-
_parseListener(listenerOrObserver) {
183-
return typeof listenerOrObserver === 'object'
184-
? listenerOrObserver.next.bind(listenerOrObserver)
185-
: listenerOrObserver;
186-
}
187-
188183
// eslint-disable-next-line @typescript-eslint/no-unused-vars
189184
onTokenChanged(onNextOrObserver, onError, onCompletion) {
190185
// iOS does not provide any native listening feature
@@ -193,7 +188,7 @@ class FirebaseAppCheckModule extends FirebaseModule {
193188
console.warn('onTokenChanged is not implemented on IOS, only for Android');
194189
return () => {};
195190
}
196-
const nextFn = this._parseListener(onNextOrObserver);
191+
const nextFn = parseListenerOrObserver(onNextOrObserver);
197192
// let errorFn = function () { };
198193
// if (onNextOrObserver.error != null) {
199194
// errorFn = onNextOrObserver.error.bind(onNextOrObserver);

packages/app/lib/common/index.js

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
*/
1717
import { Platform } from 'react-native';
1818
import Base64 from './Base64';
19-
import { isString } from './validate';
19+
import { isFunction, isObject, isString } from './validate';
2020

2121
export * from './id';
2222
export * from './path';
@@ -103,6 +103,19 @@ export function tryJSONStringify(data) {
103103
}
104104
}
105105

106+
export function parseListenerOrObserver(listenerOrObserver) {
107+
if (!isFunction(listenerOrObserver) && !isObject(listenerOrObserver)) {
108+
}
109+
if (isFunction(listenerOrObserver)) {
110+
return listenerOrObserver;
111+
}
112+
if (isObject(listenerOrObserver) && isFunction(listenerOrObserver.next)) {
113+
return listenerOrObserver.next.bind(listenerOrObserver);
114+
}
115+
116+
throw new Error("'listenerOrObserver' expected a function or an object with 'next' function.");
117+
}
118+
106119
// Used to indicate if there is no corresponding modular function
107120
const NO_REPLACEMENT = true;
108121

packages/auth/lib/index.js

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import {
2323
isOther,
2424
isString,
2525
isValidUrl,
26+
parseListenerOrObserver,
2627
} from '@react-native-firebase/app/lib/common';
2728
import { setReactNativeModule } from '@react-native-firebase/app/lib/internal/nativeModule';
2829
import {
@@ -225,14 +226,8 @@ class FirebaseAuthModule extends FirebaseModule {
225226
await this.native.setTenantId(tenantId);
226227
}
227228

228-
_parseListener(listenerOrObserver) {
229-
return typeof listenerOrObserver === 'object'
230-
? listenerOrObserver.next.bind(listenerOrObserver)
231-
: listenerOrObserver;
232-
}
233-
234229
onAuthStateChanged(listenerOrObserver) {
235-
const listener = this._parseListener(listenerOrObserver);
230+
const listener = parseListenerOrObserver(listenerOrObserver);
236231
const subscription = this.emitter.addListener(
237232
this.eventNameForApp('onAuthStateChanged'),
238233
listener,
@@ -247,7 +242,7 @@ class FirebaseAuthModule extends FirebaseModule {
247242
}
248243

249244
onIdTokenChanged(listenerOrObserver) {
250-
const listener = this._parseListener(listenerOrObserver);
245+
const listener = parseListenerOrObserver(listenerOrObserver);
251246
const subscription = this.emitter.addListener(
252247
this.eventNameForApp('onIdTokenChanged'),
253248
listener,
@@ -262,7 +257,7 @@ class FirebaseAuthModule extends FirebaseModule {
262257
}
263258

264259
onUserChanged(listenerOrObserver) {
265-
const listener = this._parseListener(listenerOrObserver);
260+
const listener = parseListenerOrObserver(listenerOrObserver);
266261
const subscription = this.emitter.addListener(this.eventNameForApp('onUserChanged'), listener);
267262
if (this._authResult) {
268263
Promise.resolve().then(() => {

packages/remote-config/e2e/config.e2e.js

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -781,11 +781,29 @@ describe('remoteConfig()', function () {
781781
});
782782
});
783783

784+
describe('onConfigUpdated parameter verification', function () {
785+
it('throws an error if no callback provided', async function () {
786+
const { getRemoteConfig, onConfigUpdated } = remoteConfigModular;
787+
try {
788+
onConfigUpdated(getRemoteConfig());
789+
} catch (error) {
790+
error.message.should.containEql(
791+
"'listenerOrObserver' expected a function or an object with 'next' function.",
792+
);
793+
}
794+
});
795+
});
796+
784797
describe('onConfigUpdated on un-supported platforms', function () {
798+
if (!Platform.other) {
799+
// Supported on non-other, tests are in the following describe block
800+
return;
801+
}
802+
785803
it('returns a descriptive error message if called', async function () {
786804
const { getRemoteConfig, onConfigUpdated } = remoteConfigModular;
787805
try {
788-
onConfigUpdated(getRemoteConfig());
806+
onConfigUpdated(getRemoteConfig(), () => {});
789807
} catch (error) {
790808
error.message.should.containEql('Not supported by the Firebase Javascript SDK');
791809
}
@@ -794,7 +812,7 @@ describe('remoteConfig()', function () {
794812

795813
xdescribe('onConfigUpdated on supported platforms', function () {
796814
if (Platform.other) {
797-
// Not supported on Web, verify we get a nice error
815+
// Not supported on Web
798816
return;
799817
}
800818

packages/remote-config/lib/index.js

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import {
2222
isString,
2323
isUndefined,
2424
isIOS,
25+
parseListenerOrObserver,
2526
} from '@react-native-firebase/app/lib/common';
2627
import Value from './RemoteConfigValue';
2728
import {
@@ -247,7 +248,7 @@ class FirebaseConfigModule extends FirebaseModule {
247248
* @returns {function} unsubscribe listener
248249
*/
249250
onConfigUpdated(listenerOrObserver) {
250-
const listener = this._parseListener(listenerOrObserver);
251+
const listener = parseListenerOrObserver(listenerOrObserver);
251252
let unsubscribed = false;
252253
const subscription = this.emitter.addListener(
253254
this.eventNameForApp('on_config_updated'),
@@ -287,12 +288,6 @@ class FirebaseConfigModule extends FirebaseModule {
287288
};
288289
}
289290

290-
_parseListener(listenerOrObserver) {
291-
return typeof listenerOrObserver === 'object'
292-
? listenerOrObserver.next.bind(listenerOrObserver)
293-
: listenerOrObserver;
294-
}
295-
296291
_updateFromConstants(constants) {
297292
// Wrapped this as we update using sync getters initially for `defaultConfig` & `settings`
298293
if (constants.lastFetchTime) {

0 commit comments

Comments
 (0)