Any document field rules that assert is int contained within firestore.rules cause the following error when a write is attempted using JavaScript integer values for those fields (see example.ts below):
Error: [firestore/permission-denied] The caller does not have permission to execute the specified operation.
This error is resolved if is int is changed to is number. I assume this is due to numeric values of any kind serialising and being sent as float values. However, allowing floats for certain use cases (document order fields, for example) poses a security risk.
This does not happen when the web SDK is used.
package.json:"@react-native-firebase/firestore": "^10.4.1"
firestore.rules:match /MyCollection/{doc} {
allow create: if request.resource.data.order is int;
}
example.ts:firestore().collection('MyCollection').doc().set({ order: 1 });
Click To Expand
**`react-native info` output:**
System:
OS: macOS 11.1
CPU: (16) x64 Intel(R) Core(TM) i9-9980HK CPU @ 2.40GHz
Memory: 28.47 GB / 64.00 GB
Shell: 5.8 - /bin/zsh
Binaries:
Node: 12.14.0 - ~/.nvm/versions/node/v12.14.0/bin/node
Yarn: 1.22.5 - ~/.yarn/bin/yarn
npm: 6.13.4 - ~/.nvm/versions/node/v12.14.0/bin/npm
Watchman: 4.9.0 - /usr/local/bin/watchman
Managers:
CocoaPods: 1.10.0 - /usr/local/bin/pod
SDKs:
iOS SDK:
Platforms: iOS 14.3, DriverKit 20.2, macOS 11.1, tvOS 14.3, watchOS 7.2
Android SDK:
API Levels: 29, 30
Build Tools: 28.0.3, 29.0.2, 30.0.0, 30.0.2
System Images: android-30 | Google Play Intel x86 Atom
Android NDK: Not Found
IDEs:
Android Studio: 4.0 AI-193.6911.18.40.6514223
Xcode: 12.3/12C33 - /usr/bin/xcodebuild
Languages:
Java: 1.8.0_272 - /usr/bin/javac
Python: 2.7.16 - /usr/bin/python
npmPackages:
@react-native-community/cli: Not Found
react: ^17.0.1 => 17.0.1
react-native: ^0.63.4 => 0.63.4
react-native-macos: Not Found
npmGlobalPackages:
*react-native*: Not Found
- **Platform that you're experiencing the issue on**:
- [ ] iOS
- [ ] Android
- [鉁旓笍] **iOS** but have not tested behavior on Android
- [ ] **Android** but have not tested behavior on iOS
- [ ] Both
- **`react-native-firebase` version you're using that has this issue:**
- `"@react-native-firebase/app": "^10.4.0"`
- **`Firebase` module(s) you're using that has the issue:**
- `"@react-native-firebase/firestore": "^10.4.1"`
- **Are you using `TypeScript`?**
- `Y` & `^4.1.3`
React Native Firebase and Invertase on Twitter for updates on the library.Indeed. This sounds like something in the serializer related to all numbers in javascript being floating point.
Our test rules are here: https://github.com/invertase/react-native-firebase/blob/master/.github/workflows/scripts/firestore.rules
We collect issue-specific tests here: https://github.com/invertase/react-native-firebase/blob/master/packages/firestore/e2e/issues.e2e.js
I bet with a combination of a new rule and a test that exercised it we could pinpoint the failure (and have a test harness for future that makes sure it's fixed forever)
Judging by the code content of the firestore seralizer it only pays attention to "number" (which does conform to javascript the language, though perhaps not firebase-js-sdk) https://github.com/invertase/react-native-firebase/blob/b4fb97698eb2579da3937bc6fed5dc4267f56aa1/packages/firestore/lib/utils/serialize.js#L24
This has a corresponding entry in the iOS code https://github.com/invertase/react-native-firebase/blob/b4fb97698eb2579da3937bc6fed5dc4267f56aa1/packages/firestore/ios/RNFBFirestore/RNFBFirestoreSerialize.m#L47
And we do chuck a floating point (well, double) value in for them: https://github.com/invertase/react-native-firebase/blob/b4fb97698eb2579da3937bc6fed5dc4267f56aa1/packages/firestore/ios/RNFBFirestore/RNFBFirestoreSerialize.m#L381
(same on Android though you only mention checking iOS at the moment https://github.com/invertase/react-native-firebase/blob/b4fb97698eb2579da3937bc6fed5dc4267f56aa1/packages/firestore/android/src/reactnative/java/io/invertase/firebase/firestore/ReactNativeFirebaseFirestoreSerialize.java#L287 )
Searching for 'integerValue' in here https://github.com/firebase/firebase-js-sdk/blob/master/packages/firestore/src/model/values.ts shows how work needs to be done to correctly separate the idea of integers from doubles for comparison etc on read
This is how it is handled when checking whether it is int or float:
https://github.com/firebase/firebase-js-sdk/blob/50abe6c4d455693ef6a3a3c1bc8ef6ab5b8bd9ea/packages/firestore/src/remote/number_serializer.ts#L56 / https://github.com/firebase/firebase-js-sdk/blob/50abe6c4d455693ef6a3a3c1bc8ef6ab5b8bd9ea/packages/firestore/src/util/types.ts#L44-L52
A quick way to try it might be to have (for android anyway) something like the solutions here https://stackoverflow.com/questions/5502548/checking-if-a-number-is-an-integer-in-java and if it's reasonable to assume the number that came through the react-native bridge to native code is a whole number, pushInteger instead of pushDouble here https://github.com/invertase/react-native-firebase/blob/b4fb97698eb2579da3937bc6fed5dc4267f56aa1/packages/firestore/android/src/reactnative/java/io/invertase/firebase/firestore/ReactNativeFirebaseFirestoreSerialize.java#L287 and similar https://stackoverflow.com/questions/9612839/is-there-a-way-to-check-if-a-variable-is-a-whole-number-c for objective-c
You can try those native edits directly by reaching right in to node_modules. In general if firestore backend is supporting integers and doubles as separate data types and we are conflating them here as all IEEE floating point numbers, a more complete solution will require more thought
indeed firestore considers them separate first-class types. :thinking: https://firebase.google.com/docs/firestore/manage-data/data-types
Thanks for getting on this so quickly @mikehardy. Yep, Firestore's number is a superset of both int and float and reasonable assumptions about integers can be made in JavaScript using Number.isInteger.
The problem then becomes the inverse, as floats without decimals (e.g. 1.0) are treated as integers by JavaScript, so might not be parsed as a floating point for a float field when serialized out to Objective-C or Java, from what I understand. The web SDK just duck types its way around this.
Well, react-native-firebase has the stated goal of emulating the firebase-js-sdk. It may be acceptable to follow the exact same duck-typing style firebase-js-sdk does, perhaps with a backwards-compatibility toggle of "all double all the time like before"
It struck me that modulo provides an excellent workaround to this problem. Should anyone else have this issue, an interim solution would be to change your Firestore rule from:
if field is int
to the functionally identical but less type specific:
if field is number && field % 1 == 0
Since that is effectively what the javascript is doing in it's duck typing, that may in fact be the "most correct" solution, certainly the "smallest change" one! I think that's clever
Much obliged. Shall we continue to leave this open for the JS SDK parity goal?
We discussed this internally and I'm not sure what we can do but it was eye-opening. I do want to leave it open as at minimum we should document the difference, the implications and the way to still get a useful result e.g. in your firestore rules modulus trick