Firebase security time rule using 'now' - firebase

I have a Firebase security rule trying to allow for writes if the field timstamp has not yet passed i.e modifications should be allowed until time has moved past the time set in the timestamp data field.
"tips": {
".read": true,
"$user_id": {
"tips": {
"$game_id": {
".write": "root.child('/games/' + $game_id + '/timestamp/').val() > now"
}
}
}
In trying to get this rule to work I have 2 sample timestamp fields of:
timestamp: 1593840696
timestamp: 1393840696
Although I'm not verifying what 'now' is I understand it to be epoch time and current date/time is around 1493840696. When writing to the Firebase I would expect the first entry to succeed with an update (it's timestamp is after 'now' time) and the second to fail as it is in the past.
What I am seeing though is that neither record is allowed to update with this rule? If I change the greater than '>' to less than '<' they both work. Strange? It would seem the value of 'now' may be greater than the 1593840696.
".write": "root.child('/games/' + $game_id + '/timestamp/').val() < now"
What would the value of 'now' be in this context? Any suggestions on how to work through/debug and achieve the required timestamp protection? Essentially need to protect/allow data changes until after the timestamp field has elapsed.
I can't get this rule to fire and protect the data so any help would be most appreciated.
Thanks for any responses.

The Firebase Database stores timestamps in milliseconds since the epoch. An easy way to get such a value is with JavaScript's Date.now() like in this snippet:
console.log(Date.now());
As I write this the value is:
1493869428756
For comparison, your highest value is:
1593840696
So it seems like you're counting seconds since the epoch, instead of milliseconds.

Related

Get Firestore Server Timestamp from the client side

I'm building an app that tracks time. It calculates the time by differentiating the seconds like so:
serverTimestamp: FieldValue // {seconds: number, milliseconds: number}.
getSeconds() {
const createdTime = new Date(this.serverTimestamp * 1000).getTime()
const currentTime = new Date().getTime();
return Math.abs((currentTime - createdTime) / 1000);
}
The problem is that Date's values equal to the current client's device clock, while firestore's timestamp equal to the firestore server, which may cause inequivalence between these two dates.
The leads me with two alternatives:
1. Save Date instead of serverTimestamp().
This way, the difference between these two dates will be accurate. Although, they won't represent the real date. Plus, if the user will change his clock on his machine, then the seconds would be changed too.
2. Create an HTTPS function that retrieves the current server timestamp.
This way, the difference between these two dates will be accurate either. Although, it feels like I'm going too far only to get the current timestamp of the server.
I am probably going to stick with alternative number two. But I was hoping if there's a better solution to achieve my goal.
PS - I'm using #angular/fire. So, solutions from this package would be welcomed.
You can also write the current timestamp with a serverTimestamp, then read the time back out of the location it was just written. Then you can make changes or calculations with that value.

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 Database Rules Timestamp Issue

Since the time shift at the 29th of October 2017 I'm running in some really strange behaviours while developing with the firebase products.
I'm developing a hybrid app with Ionic (3). While I'm developing and testing in the browser (mobile emulated device) everything works fine. As soon as I switch to my real device (Samsung Galaxy S7, no root, modded or something else), all writes to the database with a timestamp are failing.
In my code I create a timestamp like this: Date.now()
In my firebase rules I validate timestamps usualy like this:
"timestamp": {
".validate": "newData.isNumber() && newData.val() <= now"
}
For me this rule means, that the new data, which wants to be written into the database, must be a number and the value of the new data must be less than or equal to the current server timestamp. If one of these conditions doesn't match, it will throw a warning at the client.
I've spent a whole day debugging my code and finding the bug. When I remove (comment out) the .validate key on the timestamp related rules, everything works fine.
So I played a little bit with the timestamp values in the firebase rules. For example I added a little buffer to the server timestamp like: (now + 10000) (10secs).
Suddenly it worked. I decreased the value until it stopped working on my real device. I stopped at (now + 5000) (5secs).
So now my question is, why this behavior is the way it is.
Before the time shift, everything worked fine. In my understanding it couldn't be possible that a client timestamp is ahead of the server timestamps. (except the local time on the real device was modified by the user itself).
Looking for some help, the workaround with the additional 5 secs seems a bit dirty.
Cheers
Unkn0wn0x
BTW: Each time I modified the firebase rules and deployed them to the server(s), I've waited for about five minutes.
Thanks for Your annotation.
I've played one more time with the timestamps and figured out the unwanted behaviour.
This is the code, I've added to my function with wants to write a timestamp to the database:
const test = Date.now();
const test2 = new Date().getTime();
console.log('server offset: ', snap.val());
console.log('Date.now(): ', test);
console.log('new Date().getTime(): ', test2);
console.log('estimated server timestamp (new Date().getTime() + offset): ', (test2 + snap.val()));
console.log('client timestamp (Date.now() - offset): ', (test - snap.val()));
The output of the above code:
server offset: -2427
Date.now(): 1509730244926
new Date().getTime(): 1509730244926
estimated server timestamp (new Date().getTime() + offset): 1509730242499
client timestamp (Date.now() - offset): 1509730247353
The crux here is the negative offset. I've substracted the server offset from the client timestamp to get a timestamp, which is smaller then the estimated server timestamp.
But: - and - is +. So I accidentally added them both together instead of subtract from one another.
I've executed my function a few times and could determine, that the offset is from execution to execution different. One time +77 ms the other time -2427ms, etc..
So I added a little code snippet, which checks if the returned server offset is a postive or negative number, to be able to calculate the client timestamp correctly.
const serverOffset: number = snap.val();
let clientTimestamp: number = null;
if (Math.sign(serverOffset) === 1){
clientTimestamp = Date.now() - serverOffset;
} else if (Math.sign(serverOffset) === -1){
clientTimestamp = Date.now() + serverOffset;
}
clientTimestamp works now as expected.
This behaviour can also be achieved if the offset is just added up to the client timestamp as mentioned in the Firebase Docs.
Maybe it should be mentioned in the Firebase Docs, that the offset can also be negative instead of just postive. This behaviour can be easily reproduced, if the local device time runs just a second later than the time, which is fetched from the internet.
But why does this suddenly occur and never before?

How to create time-expiring data with Firebase Rules?

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.

firebase more complex validation

I'm creating an angular app with firebase back end as an API.
People will be able to book appointments on a calendar but 2 persons CANNOT share the same hour slot. Furthermore, since appointments can start every half and hour, checking becomes a bit more complex.
Can I make firebase perform some more complex validation like that? It pretty much covers everything else I need and I'd hate to create something custom, only because of that feature!
Thanks!
If I am correct, you said you don't want to do the checking client-side. Sounds good. Here's what I would do:
When storing the appointments, I would name them based on their times. Client-side code:
var dataRef = new Firebase('https://example.firebaseio.com/');
dataRef.child('2014-6-9-0500').set('name'); // 5:00 6/9/2014 converted to a string
So the appointments will all be named based on their time, and their values will be equal to the name of the person being scheduled at that time. This will make it impossible for two people to be scheduled at the same time (because in Firebase, there cannot be two children with the same name).
In your security tab, check that the data doesn't already exist (to prevent over-writing existing appointments). Then, for the validate, you could check the appointment time and make sure it ends with either "00" or "30", and is 12 digits long. The rules would look something like this:
"rules": {
".read": true,
"$time": {
".write": "!data.exists()",
".validate": "$time.endsWith('00') || $time.endsWith('30')"
}
}
Although it's possible for a nonvalid time to be accepted (such as "0000-0-0-0030" or even "qt00"), a valid appointment which is submitted will not be scheduled at the same time as another appointment, and the appointments will be at times ending at ":00" or ":30" (half-hour intervals).
The only problem is if a person has an hour-long appointment starting at noon. You would have to schedule multiple appointments: one at 12:00, and another at 12:30. That could get a little annoying, but I don't know of any other way to do it.
In my understanding, it is possible with Firebase. However, Firebase does not have the dynamic querying capabilities like Mongo does. I believe you have to take a pretty primitive approach to solving this issue by doing something like:
db.child('calendar').once('value', function(snapshot){
var hours = snapshot.val();
// for each hour
// do validation here
})

Resources