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.
Related
I have a Firebase project where I'd like for users to be able to see when other users created their profiles. My initial hope was that I could use "user.metadata.creationTime" on the frontend to pass the date into the user's extra info document and verify that it is correct by having "request.resource.data.datecreated == request.auth.metadata.creationTime" as a Database Rule, but it looks like it is not possible according to the documentation.
Is there any way I can verify that the creation date is correct on the backend?
More info edit: Below is the code that is being triggered when a user creates a new account on my profile. The three values are displayed publicly. I'm creating a niche gear for sale page so being able to see when a user first created their account could be helpful when deciding if a seller is sketchy. I don't want someone to be able to make it seem like they have been around for longer than they have been.
db.collection('users').doc(user.uid).set({
username: "Username-156135",
bio: "Add a bio",
created: user.metadata.creationTime
});
Firestore rules:
match /users/{id} {
allow get;
allow create, update: if request.resource.data.username is string &&
request.resource.data.bio is string &&
request.resource.data.created == request.auth.metadata.creationTime;
}
user.metadata.creationTime, according to the API documentation is a string with no documented format. I suggest not using it. In fact, what you're trying to do seems impossible since that value isn't available in the API documentation for request.auth.
What I suggest you do instead is use a Firebase Auth onCreate trigger with Cloud Functions to automatically create that document with the current time as a proper timestamp. Then, in security rules, I wouldn't even give the user the ability to change that field, so you can be sure it was only ever set accurately by the trigger. You might be interested in this solution overall.
I am developing a simple chat website using Firebase Firestore. And it obvious to store the message time .
Now the thing is that document is added from client side. So malcius user can add document with fake time. Is there any way avoid the scenario.
I have tried using cloud functions but it's taking too long lo send message..
You want to set the message time property to equal Firebase Server timestamp which on submit will set it on the creation of the message using request.time you can validate it equals now.
Security Rules
allow create: if request.resource.data.messageTime == request.time &&
// other rules for the message body
Client side JS code
const message = {
text: 'Hello',
messageTime: firebase.firestore.FieldValue.serverTimestamp();
}
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.
How do I check if user on client sided created document with only firebase.firestore.FieldValue.serverTimestamp()?
I have following:
allow create: if request.resource.data.timestamp == ??
What should I have instead of ??. I have tried serverTimestamp() firebase.firestore.FieldValue.serverTimestamp(), now or now() but it doesn't work.
It is possible to do it in Firebase like this:
".validate": "newData.child('timestamp').val() === now"
I am looking for the same solution. Any ideas? Thanks
You can access the current request timestamp in Security Rules using the request.time attribute (docs), which is the Firestore equivalent to the Realtime Databases's now. You'll therefore want something like:
allow create: if request.resource.data.timestamp == request.time;
For serverTimestamp() this should evaluate to true.
You should always validate client input in Security Rules, even if you're using serverTimestamp(). Security Rules doesn't automatically know the server input the value instead of the client, so without this check, a malicious client could create a different created at time.
This talk mentions time-expiring data using Firebase rules at 22:55
https://www.youtube.com/watch?v=PUBnlbjZFAI
How can one do this ?
I didn't find any information regarding this.
I recommend two solutions.
1) Use cloud functions to record a message path and the date it was posted. Then every hour sort that list by date, pick all the expired ones, and create a deep update object to null out every expired message. Nowadays you can use Cron Scheduler to handle the periodic flush.
2) Make a rule that says anyone can delete expired messages and make it so that clients automatically delete expired messages when they are in a chat room.
Written here: https://firebase.google.com/docs/database/security/securing-data
You can't have it auto delete your data but you can make them unreadable (which is the same thing from the user standpoint). Just send a timestamp child field with you data and check against it.
{
"rules": {
"messages": {
"$message": {
// only messages from the last ten minutes can be read
".read": "data.child('timestamp').val() > (now - 600000)",
// new messages must have a string content and a number timestamp
".validate": "newData.hasChildren(['content', 'timestamp']) && newData.child('content').isString() && newData.child('timestamp').isNumber()"
}
}
}
}
Same question here.
You can't do it using firebase rules. You should either have a NodeJS backend removing your old data or clients doing it for you. For example, before a client retrieves data, he could remove old data.