When are writeFields specified in Firestore requests and what replaces them? - firebase

The simulator now displays an error message trying to access request.writeFields.
Before that writeFields in Firestore Security Rules did just not work in real requests.
The message states the following:
The simulator only simulates client SDK calls; request.writeFields is always null for these simulations
Does this mean that writeFields are only specified in HTTP requests?
The documentation only states this:
writeFields: List of fields being written in a write request.
A problem that arises from this
I am searching for something that replaces this property because it is "always null".
request.resource.data in update also contains fields that are not in the requests, but already in the document to my knowledge.
Example
// Existing document:
document:
- name: "Peter"
- age: 52
- profession: "Baker"
// Update call:
document:
- age: 53
// request.resource.data in allow update contains the following:
document:
- name: "Peter"
- age: 53
- profession: "Baker"
But I only want age.

EDIT Mar 4, 2020: Map.diff() replaces writeFields functionality
The Map.diff() function gives the difference between two maps:
https://firebase.google.com/docs/reference/rules/rules.Map#diff
To use it in rules:
// Returns a MapDiff object
map1.diff(map2)
A MapDiff object has the following methods
addedKeys() // a set of strings of keys that are in after but not before
removedKeys() // a set of strings of keys that are in before but not after
changedKeys() // a set of strings of keys that are in both maps but have different values
affectedKeys() // a set of strings that's the union of addedKeys() + removedKeys() + updatedKeys()
unchangedKeys() // a set of strings of keys that are in both maps and have the same value in both
For example:
// This rule only allows updates where "a" is the only field affected
request.resource.data.diff(resource.data).affectedKeys().hasOnly(["a"])
EDIT Oct 4, 2018: writeFields is no longer supported by Firestore and its functionality will eventually be removed.
writeFields is still valid, as you can see from the linked documentation. What the error message in the simulator is telling you is that it's unable to simulate writeFields, as it only works with requests coming from client SDKs. The simulator itself seems to be incapable of simulating requests exactly as required in order for writeFields to be tested. So, if you write rules that use writeFields, you'll have to test them by using a client SDK to perform the read or write that would trigger the rule.

Related

Is there a way with Hasura to do a mutation based on the result of a query, within the same GraphQL call (Hasura transaction)?

I tried to search for an example but, I presume it's not doable. I am looking to hopefully be proven wrong or to find an official confirmation that it's not doable.
Before using Hasura, I was doing transactional SQL queries that ensured that data was kept consistent.
For example, I would like to create a password reset token if a user requests it, only if the user can be found using an email address. Right now, I have to do 2 queries:
Try to find a user with the specified email address
Insert and assign the token to this user id
In that case, it's not too bad, but now if I want to consume that token, I have to do 3 queries:
Find the valid token
Change the password to the user associated with that token
Delete the token
Obviously, if something goes wrong and the token is not deleted, this could be an issue - so I would be curious to see if there would be ways to merge these queries/mutations into transactions.
Sounds like supporting nested updates would solve this problem for you with the least amount of effort. We are working on a rfc for the feature and hope to start development soon. Please follow this Github issue on our community for future updates.
https://github.com/hasura/graphql-engine/issues/1573
This comment outlines the current scope of the proposed feature. The rfc will provide a more complete explanation.
https://github.com/hasura/graphql-engine/issues/1573#issuecomment-1338057350
You can apply changes to rows that you filter by certain criteria. Here is a sample mutation:
mutation PasswordUpdate($id: uuid!, $token: String!, $new_password: String!) {
update_user(
where: {id: {_eq: $id}, token: {_eq: $token}}
_set: {token: null, password: $new_password}
) {
affected_rows
}
}
That query deletes the token and sets a password for all users (hopefully just one) that have the token assigned.
After some research here is what I found:
For the first example:
Try to find a user with the specified email address
Insert and assign the token to this user id
There are no solutions for this today and as answered by #damel, there is an ongoing RFC to support nested mutations: https://github.com/hasura/graphql-engine/issues/1573#issuecomment-1338057350
Hopefully, this feature will be out soon, but in the meantime, for most cases, it's not such a big deal to have multiple queries as it is possible to catch errors on the first query.
For the second example:
Find the valid token
Change the password to the user associated with that token
Delete the token
When sending multiple mutations in the same query, Hasura treats them as a transaction as announced in 2020.
Of course, it would be nice to do this in the same query (similar to the first example) but since there is a transaction on the mutation, for this case it's still not a problem.
I am sure there are probably cases where this can become a problem but I am not exposed to them right now. Nevertheless, it would be great if the RFC makes it to production, giving more options to Hasura users.

Set 'durationDays' to 'from the beginning' in firebase dynamic links analytics api

Below url is used for fetching analytics data related to firebase dynamic links:
https://firebasedynamiclinks.googleapis.com/v1/SHORT_DYNAMIC_LINK/linkStats?durationDays=DURATION
(Please note that the above url is an API that needs authentication/token. You can't open it in a browser. Also, SHORT_DYNAMIC_LINK and DURATION are just placeholders not actual parameter values.
The reason I have added a link is because my question is about the value of the placeholder - DURATION present in the link)
DURATION tells how many days(going backwards) worth data needs to be fetched.
My requirement is to fetch data from the start(when the dynamic link was created). So, what value should I set for durationDays to achieve that?
As a workaround I can set a big number(like 1000 days) but wanted to know a proper way.
Based on what I tried, the parameter durationDays must be present in the url, otherwise the request would return 400 INVALID ARGUMENT. Even setting DURATION values to 0 or -1 return a similar error.
Firebase Analytics API Doc: https://firebase.google.com/docs/reference/dynamic-links/analytics

Firestore Rules verify timestamp with a Flutter client

I want to send the creation time of a Firestore document through the client and verify the time with Firestore Rules to avoid Cloud Functions calls (pricing).
Scenario
I am testing requests from clients against Firestore rules like this:
allow create: if request.resource.data.TIMEFIELD == request.time;
The request contains a TIMEFIELD that has a timestamp, just like request.time.
Problem
Apparently the request time and the time I am setting as a field right before sending the request are not equivalent, which makes this comparison impossible.
The following is the defition of request.time from the documentation.
When the request was received by the service.
I wonder if there is a way to set a field in a document equal to request.time.
I am unable to use server side timestamps because of an issue with Flutter.
Because of that I need to know how I could possibly validate client side timestamps like time.now with Firestore Rules.
You can use the Timestamp to add constraints to the time field (docs).
Here is an example of how to ensure that the change was within a certain amount of seconds:
function withinSeconds(secs) {
return request.resource.data.TIMEFIELD.seconds() - request.time.seconds() <= secs
&& request.resource.data.TIMEFIELD.seconds() - request.time.seconds() >= -secs
}
Edit
The above is for setting the value within a threshold of the request.time.
You can also just use the REST API in the mean time. Just make a write request that includes an update and a transform. The transform is where you would set the server timestamp. Here is a tool to help understand how to build the requests.
This has been implemented into the Flutter plugin for Cloud Firestore:
FieldValue.serverTimestamp()
Using this as a field's value will assign a timestamp equal to request.time to the field, server-side.
You can find out more about it in the API reference for cloud_firestore.
you'd first have to remember the creation (or last updated) timestamp:
firestore().collection("items").add({
....
created: firebase.firestore.FieldValue.serverTimestamp()
});
in order to let the client know of the timestamp, which you are trying to compare later on.

Firebase writes different files if app is debug or release version

I just found out something very odd with Firebase and I would like to know if it is me that's doing something wrong or if there is a solution to this problem.
Basically, this is what it has always written when I was developing the app (and it's precisely what I was expecting):
nscoachtools#gmail¸com
maxMatches: 60
maxPlayers: 500
maxTeams: 30
userId: "SnMuRZEVqyN***...***hv2"
userMail: "nscoachtools#gmail.com"
userName: "Nicola Salvaro"
userPicture: "https://lh4.googleusercontent.com/-L7lSPz0VJ9A/..."
userToken: -1
and this is what it writes after I built the app in release mode:
nsalvaro77#gmail¸com
a: "Nicola Salvaro"
b: "ESjqwuh***...***wg1"
c: "nsalvaro77#gmail.com"
d: "https://lh4.googleusercontent.com/-2kwSEmLEN1c/..."
e: -2
f: 30
g: 500
h: 60
userToken: 1499775285255
Every "title" has been replaced with a letter. And "e: " was supposed to be "userToken: " then, when I tried to update it, it wrote it with the proper string but not on top of the original value... just wrote a new one. Then, when I try to read the full user, it gets the value of "e: ", not the "userToken: " one.
Did I do something wrong?
In release mode your Android app is being minified by Proguard. This process strips unused methods and makes other method names shorter.
As a consequence, your POJO classes (the classes your read from/write to Firebase) are getting new method names and Firebase reflectively uses those method names to determine the properties in the JSON.
The solution is to tell Proguard to not modify the method names of your POJOs.
More on that:
The oldest Q&A on how to do this is: What ProGuard configuration do I need for Firebase on Android?. But that one is from Firebase 2.x, while many of these are auto-included in Firebase 9 and up.
You can also potentially mark the classes with #Keep, see Firebase No properties to serialize found on class.
More interesting Q&A on this topic: https://stackoverflow.com/search?q=%5Bfirebase-database%5D+proguard+release

Having consistency during multi path updates when the paths are not deterministic and are variable

I need help in a scenario when we do multipath updates to a fan-out data. When we calculate the number of paths and then update, in between that, if a new path is added somewhere, the data would be inconsistent in the newly added path.
For example below is the data of blog posts. The posts can be tagged by multiple terms like “tag1”, “tag2”. In order to find how many posts are tagged with a specific tag I can fanout the posts data to the tags path path as well:
/posts/postid1:{“Title”:”Title 1”, “body”: “About Firebase”, “tags”: {“tag1:true, “tag2”: true}}
/tags/tag1/postid1: {“Title”:”Title 1”, “body”: “About Firebase”}
/tags/tag2/postid1: {“Title”:”Title 1”, “body”: “About Firebase”}
Now consider concurrently,
1a) that User1 wants to modify title of postid1 and he builds following multi-path update:
/posts/postid1/Title : “Title 1 modified”
/tags/tag1/postid1/Title : “Title 1 modified”
/tags/tag2/postid1/Title : “Title 1 modified”
1b) At the same time User2 wants to add tag3 to the postid1 and build following multi-path update:
/posts/postid1/tags : {“tag1:true, “tag2”: true, “tag3”: true}
/tags/tag3/postid1: {“Title”:”Title 1”, “body”: “About Firebase”}
So apparently both updates can succeed one after other and we can have tags/tag3/postid1 data out of sync as it has old title.
I can think of security rules to handle this but then not sure if this is correct or will work.
Like we can have updatedAt and lastUpdatedAt fields and we have check if we are updating our own version of post that we read:
posts":{
"$postid":{
".write":true,
".read":true,
".validate": "
newData.hasChildren(['userId', 'updatedAt', 'lastUpdated', 'Title']) && (
!data.exists() ||
data.child('updatedAt').val() === newData.child('lastUpdated').val())"
}
}
Also for tags we do not want to check that again and we can check if /tags/$tag/$postid/updatedAt is same as /posts/$postid/updatedAt.
"tags":{
"$tag":{
"$postid":{
".write":true,
".read":true,
".validate": "
newData.hasChildren(['userId', 'updatedAt', 'lastUpdated', 'Title']) && (
newData.child('updatedAt').val() === root.child('posts').child('$postid').val().child('updatedAt').val())”
}
}
}
By this “/posts/$postid” has concurrency control in it and users can write their own reads
Also /posts/$postid” becomes source of truth and rest other fan-out paths check if updatedAt fields matches with it the primary source of truth path.
Will this bring in consistency or there are still problems? Or can bring performance down when done at scale?
Are multi path updates and rules atomic together by that I mean a rule or both rules are evaluated separately in isolation for multi path updates like 1a and 1b above?
Unfortunately, Firebase does not provide any guarantees, or mechanisms, to provide the level of determinism you're looking for. I have had the best luck front-ending such updates with an API stack (GCF and Lambda are both very easy, server-less methods of doing this). The updates can be made in that layer, and even serialized if absolutely necessary. But there isn't a safe way to do this in Firebase itself.
There are numerous "hack" options you could apply. You could, for example, have a simple lock mechanism using a dedicated collection for tracking write locks. Clients could post to a lock collection, then verify that their key was the only member of that collection, before performing a write. But I hope you'll agree with me that such cooperative systems have too many potential edge cases, potential security issues, and so on. In Firebase, it is best to design such that this component is not a requirement in the first place.

Resources