I have a Mongoose schema with the timestamp option set as true.
schema = new mongoose.Schema({
...
},
{ timestamps: true });
Now I have an Android application that gets the timestamp using System.currentTimeMillis() which works all well and good and gives me number of milliseconds since UNIX epoch time.
I send this data across to my Node.js / Express server which takes the time and returns only documents created after that particular date.
// get all docs
router.get('/api/seekers', function(req, res) {
Seeker.find({createdAt:{ $gt: new Date(req.query.timestamp) }}, function(err, seekers) {
if(err)
res.send(err);
else
res.json(seekers);
});
});
So I send https://api_url.com/api/seekers?timestamp=1479431351762 as a request to the server.
Now a couple of things happen:
I send the value in milliseconds and get this error
{"message":"Cast to date failed for value \"Invalid Date\" at path \"updatedAt\"","name":"CastError","kind":"date","value":null,"path":"updatedAt"}
After a little bit of investigation, it turns out you need to pass seconds to Date(). So,
I divide the value by 1000 to get seconds (req.query.timestamp/1000). Now I wasn't getting an error, but the query constraint was not working. I'm getting all values since the beginning of time.
I moved over to the Mongo shell to check whether the issue persists, to which it turns out it doesn't since I can pass the millisecond value to Mongo's Date:
> new Date(1479431351762)
ISODate("2016-11-18T01:09:11.762Z")
But if I tried passing the second value to Date(), it turns out it actually was sending me to the beginning of time:
> new Date(1479431351)
ISODate("1970-01-18T02:57:11.351Z")
I'm unable to figure this out, what can I do so server request and mongoose correctly processes the timestamp and queries my db?
Any wanderers with the same issue, who might have missed chridam's comment, you just need to cast the passed timestamp to int before parsing. This works:
new Date(parseInt(req.query.timestamp))
Related
I have an app that uses the Firebase SDK to directly talk to Cloud Firestore from within the application. My code makes sure to only write data at reasonable intervals. But a malicious user might take the configuration data from my app, and use it to write an endless stream of data to my database.
How can I make sure a user can only write say once every few seconds, without having to write any server-side code.
Every read or write operation to your database, is validated on Google's servers by the security rules that you configured for your project. These rules can only be set by collaborators on your project, but apply to all client-side code that accesses the database in your project. This means that you can enforce this condition in these security rules, not even the malicious user can bypass them, since they don't have access to your project.
Say we have a users collection, and that each document in there has an ID with the UID of the user. These security rules make sure that the user can only write their own document, and no more than once every 5 seconds:
match /users/{document=**} {
allow create: if isMine() && hasTimestamp();
allow update: if isMine() && hasTimestamp() && isCalm();
function isMine() {
return request.resource.id == request.auth.uid;
}
function hasTimestamp() {
return request.resource.data.timestamp == request.time;
}
function isCalm() {
return request.time > resource.data.timestamp + duration.value(5, 's');
}
}
A walkthrough might help:
The first line determines the scope of the rules within them, so these rules apply to all documents within the /users collection.
A user can create a document if it's theirs (isMine()), if it has a timestamp (hasTimestamp()).
A user can update a document, if it's theirs, has a timestamp, and and if they don't write too often (isCalm()).
Let's look at all three functions in turn...
The isMine() function checks if the document ID is the same as the user who is performing the write operation. Since auth.uid is populated by Firebase automatically based on the user who is signed in, there is no way for a malicious user to spoof this value.
The hasTimestamp() function checks if the document that is being written (request.resource) has a timestamp field, and if so, if that timestamp is the same as the current server-side time. This means that in code, you will need to specify FieldValue.serverTimestamp() in order for the write to be acceptable. So you can only write the current server-side timestamp, and a malicious user can't pass in a different timestamp.
The isCalm() functions makes sure the user doesn't write too often. It allows the write if the difference between the timestamp values in the existing document (resource.data.timestamp) and the document (request.resource.data.timestamp) that is currently being written, is at least 5 seconds.
Per Doug's comment:
It's important to note that the above implements a per-document write limit, and not a per-account limit. The user is still free to write other documents as fast as the system allows.
Continue reading if you want to have a per-user write rate-limit, on all documents they write.
Here's a jsbin of how I tested these rules: https://jsbin.com/kejobej/2/edit?js,console. With this code:
firebase.auth().signInAnonymously().then(function(auth) {
var doc = collection.doc(auth.user.uid);
doc.set({
timestamp: firebase.firestore.FieldValue.serverTimestamp()
}).then(function() {
console.log("Written at "+new Date());
}).catch(function(error) {
console.error(error.code);
})
})
If you repeatedly click the Run button, it will only allow a next write if at least 5 seconds have passed since the previous one.
When I click the Run button about once a second, I got:
"Written at Thu Jun 06 2019 20:20:19 GMT-0700 (Pacific Daylight Time)"
"permission-denied"
"permission-denied"
"permission-denied"
"permission-denied"
"Written at Thu Jun 06 2019 20:20:24 GMT-0700 (Pacific Daylight Time)"
"permission-denied"
"permission-denied"
"permission-denied"
"permission-denied"
"Written at Thu Jun 06 2019 20:20:30 GMT-0700 (Pacific Daylight Time)"
The final example is a per-user write rate-limit. Say you have a social media application, where users create posts, and each user has a profile. So we have two collections: posts and users. And we want to ensure that a user can create a new post at most once every 5 seconds.
The rules for this are pretty much the same as before, as in: a user can update their own profile, and can create a post if they haven't written one in the past 5 seconds.
The big different is that we store the timestamp in their user profile (/users/$uid), even when they're creating a new post document (/posts/$newid). Since both of these writes need to happen as one, we'll use a BatchedWrite this time around:
var root = firebase.firestore();
var users = root.collection("users");
var posts = root.collection("posts");
firebase.auth().signInAnonymously().then(function(auth) {
var batch = db.batch();
var userDoc = users.doc(auth.user.uid);
batch.set(userDoc, {
timestamp: firebase.firestore.FieldValue.serverTimestamp()
})
batch.set(posts.doc(), {
title: "Hello world"
});
batch.commit().then(function() {
console.log("Written at "+new Date());
}).catch(function(error) {
console.error(error.code);
})
})
So the batch writes two things:
It writes the current server-side time to the user's profile.
It creates a new post with a title field.
The top-level security rules for this are (as said) pretty much the same as before:
match /users/{user} {
allow write: if isMine() && hasTimestamp();
}
match /posts/{post} {
allow write: if isCalm();
}
So a user can write to a profile doc if it's their own, and if that doc contains a timestamp that is equal to the current server-side/request time. A user can write a post, if they haven't posted too recently.
The implementation of isMine() and hasTimstamp() is the same as before. But the implementation of isCalm() now looks up the user profile document both before and after the write operation to do its timestamp check:
function isCalm() {
return getAfter(/databases/$(database)/documents/users/$(request.auth.uid)).data.timestamp
> get(/databases/$(database)/documents/users/$(request.auth.uid)).data.timestamp + duration.value(5, 's');
}
The path to get() and getAfter() unfortunately has to be absolute and fully qualified, but it boils down to this:
// These won't work, but are easier to read.
function isCalm() {
return getAfter(/users/$(request.auth.uid)).data.timestamp
> get(/users/$(request.auth.uid)).data.timestamp + duration.value(5, 's');
}
A few things to note:
Just like before we're comparing two timestamps. But here we're reading the timestamps from different documents.
This requires reading two extra documents, which means you'll be charged for two extra read operations. If the purpose of the rate limit is to not be charged for the write operations of a malicious user, this may not be the solution you're looking for.
I want to add a timestamp of a data entry creation, example:
myRef.child("uid").setValue(data);
//add timestamp to the same path myRef.child("uid").setValue(<timestamp here>)
What would be the best timestamp, so that it will be independent of any time zone? (For example if a user's phone clock is set to the wrong time)
I did see this article from Firebae docs: Timestamp, but can't figure how to use it.
Use the value placeholder firebase.database.ServerValue.TIMESTAMP to update a value with the current clock time on the server at the time of the write, as seen by the server. It's a token value that means nothing on the client but has a special meaning on the server when it's written.
inside Firebase Functions transform the timestamp like so:
timestampObj.toDate()
timestampObj.toMillis().toString()
documentation here https://firebase.google.com/docs/reference/js/firebase.firestore.Timestamp
i am trying to use compareTo method based on firebase docs to get how long ago was the database entry created.
https://firebase.google.com/docs/reference/android/com/google/firebase/Timestamp
I am trying to do something like this
var postedAgo = myEntry.dateCreated.compareTo(firebase.database.ServerValue.TIMESTAMP);
myEntry.dateCreated is stored TIMESTAMP, so all i got in my db is numerical value. I hope thats right. But the problem is when i log this it says that compareTo is not a function.
I am obviously doing something wrong, but i cant find almost anything on compareTo beyond the documentation. I wonder if anyone is even using it.
Thanks
Luke
The Firebase Database ServerValue.TIMESTAMP is not an actual value of a timestamp, but a so-called marker value that the server recognized (and then replaces by the current timestamp when writing to the database). Because of this, you cannot use ServerValue.TIMESTAMP in pure client-side operations.
If you want to determine how long ago a node was created, you'd take the timestamp from that node and subtract it from the current timestamp. So if myEntry.dateCreated is a timestamp that was written with ServerValue.TIMESTAMP, you could do:
System.currentTimeMillis() - myEntry.dateCreated
I have a very weired problem with my app while I try to add a birthday event on a picked contact.
My code for this is:
int mret = np2.getValue()+1;
ContentResolver cr = v.getContext().getContentResolver();
ContentValues values = new ContentValues();
values.put(ContactsContract.RawContacts.Data.MIMETYPE, ContactsContract.CommonDataKinds.Event.CONTENT_ITEM_TYPE);
values.put(ContactsContract.RawContacts.Data.RAW_CONTACT_ID, rawContactId);
values.put(ContactsContract.CommonDataKinds.Event.TYPE, ContactsContract.CommonDataKinds.Event.TYPE_BIRTHDAY);
if (boolyear == true) {
values.put(ContactsContract.CommonDataKinds.Event.START_DATE, np3.getValue() + "-" + mret + "-" + np1.getValue());
} else {
values.put(ContactsContract.CommonDataKinds.Event.START_DATE, "0000-" + mret + "-" + np1.getValue());
}
Uri bduri= null;
try {
bduri= cr.insert(ContactsContract.Data.CONTENT_URI,values);
} catch (Exception e) {}
The above code works fine as it creates the birthday event but a few seconds later appears a second birthday entry which possibly is a result of sync as it happens only if there is an Internet connection.
This is not a problem of the device because other apps downloaded from Play Store work as expected. It's only my app that creates double birthday entries.
Why is this happening and how to fix that?
Thank you in advance.
UPDATE: I managed to fix it. The problem was the date format. All values (day and month) must be in a two-digit format. E.g. "1980-07-01", not "1980-7-1".
I managed to fix it. The problem was the date format. All values (day and month) must be in a two-digit format. E.g. "1980-07-01", not "1980-7-1".
I am assuming that you are using a Sync Adapter to interface with the Calendar.
The Sync Adapter has two important methods:
onPerformSync(...), and onSyncCanceled(...)
onPerformSync is called whenever you make a ContentResolver.requestSync call or if automatic sync is set to true.
While executing, onPerformSync can be interrupted by Android. This can happen if the device is running low on resources of if your app is not in the foreground anymore.
When interupted, the Sync Adapter will stop execution where it's at and will call onSyncCanceled. The default behavior of the Sync Adapter is to retry the failed sync messages the first chance it gets. So if your app manages to grab onto OS resources again, it will replay the interrupted sync message.
It is possible that some of your messages are being fully processed and, just before onPerformSync is about to complete, the Sync Adapter is interrupted. At this point you managed to save the event, however, the Sync Adapter believes that the sync failed, and therefore will replay the same message again the next time it tries to sync.
I am not sure if you are syncing one event at a time so I can't offer a definitive solution. However, what you can do is change the default behaviour of the Sync Adapter to stop it from replaying "failed" messages.
Bundle extras = new Bundle();
...
extras.putBoolean(ContentResolver.SYNC_EXTRAS_DO_NOT_RETRY, true);
CalendarConstants.AUTHORITY, true);
ContentResolver.requestSync(mAccount, CalendarConstants.AUTHORITY, extras);
In OnSyncCanceled, you can do some light checks to make sure the message succeeded, i.e. is present in the calendar's events table. If not, then manually replay it. If it is present, the SyncAdapter will simply move on to the next message, and you won't get any duplicates.
Alternatively, you could make sure all operations within onPerformSync are atomic.
I'm using firestore in my android project, everything working fine.But Yesterday an issue occurred on getting timestamp value from document snapshot.
if (documentChange.getType() == DocumentChange.Type.ADDED) {
Map<String, Object> stringObjectMap = documentChange.getDocument().getData();
Date date = (Date) stringObjectMap.get("timestamp"); }
This is one of my Collection Document.
id : "25",
message : "This is my message",
timestamp : December 11, 2017 at 10:39:12 PM UTC+5, \\This is Firebase FieldValue.serverTimestamp()
username : "Temp"
I get everything from the document except for timestamp.
If you are seeing this ADDED event with a null timestamp after writing from the same client, this is the expected behavior.
When you write with server timestamps you will get two events:
An initial event confirming the document write in the local cache.
At this point the timestamp is null because the server has not set
it yet.
A second event where the timestamp has been set by the server and
the client has observed the change.
We are working on an API to make this behavior configurable, but right now you need to handle this case.
Edit You can now control this behavior.
DocumentSnapshot snap = ...
// One of ESTIMATE, NONE, or PREVIOUS
DocumentSnapshot.ServerTimestampBehavior behavior = ...
Date date = snap.getDate("fieldName", behavior);
The previous behavior (returning null when not known) is equivalent to NONE in the new API. For more information, see the JavaDoc:
https://firebase.google.com/docs/reference/android/com/google/firebase/firestore/DocumentSnapshot.ServerTimestampBehavior