Firebase security - Transactions - firebase

Using Transactions in Firebase is a great way to atomically modify the data, but how do I know that the user actually uses my code to insert data?
For example, what if the user gets a reference to the data location (using the browser console) and overwrites the previous data using set rather than clicking on the my pre-designed button which uses transaction in the background?
Update (an example):
var wilmaRef = new Firebase('https://docs-examples.firebaseio.com/samplechat/users/wilma');
wilmaRef.transaction(function(currentData) {
if (currentData === null) {
return { name: { first: 'Wilma', last: 'Flintstone' } };
} else {
console.log('User wilma already exists.');
return; // Abort the transaction.
}
});
Now, what if the user uses:
wilmaRef.set({name: { first: 'Wilma', last: 'Flintstone' }});

The Firebase Database has no way to ensure that it's a specific piece of code that makes a modification. See my answer to this question for more on why knowing the URL of a resource is not a security risk: How to restrict Firebase data modification?
Firebase security works based on knowing who the user is and allowing them specific read/write operations based on that knowledge. Once you take that mindset, it doesn't matter if someone uses a JavaScript console to make changes to the database that is behind your Android app. As long as the JavaScript follows the rules that you've set for the user that runs it, the changes to the database are permitted.

Related

Flutter + Firebase - Check User Presence

I am trying to remove users from database (Firebase Realtime Database) who are away or have disconnected. I did my search and the only resource I could find related to this was:
https://firebase.google.com/docs/firestore/solutions/presence
The link is a web solution, I have tried to adopt the concept to try to make it work:
#override
void initState() {
super.initState();
checkConnection();
}
checkConnection() {
databaseReference.child('.info/connected').onValue.listen((data) {
if (data.snapshot.value == false) {
return;
}
databaseReference
.child('games')
.child(inviteCode)
.child("players")
.child(playerID)
.onDisconnect()
.remove();
});
}
The above code doesn't seem to work. I have tried testing it on iOS simulator.
I am not using Firebase Authenticator. I am simply adding users directly to the Real Time Database and the structure is as follows:
games { inviteCodehere: { players: {-M_AUmwDhQBzFdPL1lsE: {name: saad, score: 0 } } } }
Would appreciate if someone could guide me. And is there a way I can define the trigger for how long the user is away? If not, what is the default value. Thank you
How do you test if it is working or not?
It can take some time for it to get triggered. Because you also search for something to indicate for how long the user is gone maybe this would help you.
You can make a combination of the onDisconnect event and a periodic is alive timestamp you save to the database each time a user does something in the app.
The RealtimeDatabase SDK for flutter is still in beta so I would recommend the is alive solution and if you need to delete some data if someone is gone for a specific time. Create a cloud function and query the timestamps older than a specific time and delete the data related to them.

Prevent firebase function from overwriting existing data

I am moving the process of creating users in my application to a firebase function for a couple of reasons but I am running into an issue:
I have a /users ref and a /usernames, when a user is created I persist their info in users and usernames (which is publicly accessible to see if a username is available) as a transaction so the username is added immediately when a user is created and my security rules prevent overriding existing data.
However, with firebase functions these security rules are bypassed so there could be a case where 2 users signup with the same username and one person's data will be overriden by the other
is there a way to prevent overriding existing data from cloud functions? (ideally without having them go through the security rules)
I ran into a similar issue and the best solution i found was using the transaction method that firebase offers.
assuming you have a usernames ref you could do something like this:
db.ref('usernames').child(theUsername).transaction(function (usernameInfo) {
if (usernameInfo === null) {
return {
...anObjectOfUserData // or could just return true
}
}
// if the data is not null it means the username is used
// returning nothing makes the transaction do no updates
return
}, function (error, isComitted, snap) {
//
// Use isCommitted from the onComplete function to determine if data was commited
// DO NOT USE the onUpdate function to do this as it will almost certainly run a couple of times
//
})

How to prevent collection modification via console for an otherwise secure update operation?

I am fairly new to Meteor and am just trying to figure out meteor security.
I am writing a quiz app that allows a logged in user to save their scores. I have created a collection which consists of a user id and an array of scores. The way I expose a push of new score is a method on the server side:
Meteor.methods({
'pushScore' : function(playerId, playerScore) {
UserScores.upsert({ userId : playerId}, {$push : {scores : playerScore}});
}
});
I call the method on click of a button from the client like so:
if (Meteor.userId()){
Meteor.call('pushScore', Meteor.userId(), Session.get("score"));
}
I have the following concerns here:
Obviously the user can manipulate the score value in "Session" and cheat the system. What could be an alternate secure mechanism to keep track of the running score while a quiz is being taken?
The other one is probably a bigger concern. How do I prevent the user from just firing a console call to my method "pushScore" and again cheat the system by adding, say a score of 100?
Is there an inherent flaw in the way I have designed here?
This is just a sample application, but I can easily imagine a real world scenario which could mimic this. What woudl be a best practice in such a scenario?
Thanks in advance.
Cheers..
As #Peppe suggested, you should move the logic to the server somehow. The main rule for Meteor security (and web security in general) is
You cannot trust the client.
The reason for that is what you've already mentioned: if there is something a client can do, then there is no way to stop a rogue user to do the same thing from the browser console, or even to write his own malicious client that will exploit the leak.
In your case, that means that if client is able to add points to scores, then the user is able to do so as well, regardless on what security measures you employ. You can make this more or less difficult, but your system has a designed leak which cannot be completely closed.
Thus, the only bulletproof solution is to make the server decide on when to assign points. I assume that in a quiz app user gets points when he choose the right answer to a question. So instead of checking that on the client, create a server–side method that will receive the question ID, answer ID, and increase user scores if the answer is correct. Then make sure user cannot just call this method with all possible answer, with a way that corresponds to your quiz design – for example give negative points if wrong answer is chosen, or allow to answer the same question only once in a period of time.
Finally, make sure the client doesn't just get the correct answer ID in the data it receives.
In a nutshell, there are 2 common soloutions to your problem:
if you're using a Meteor.method dont pass any arguments in the Meteor.call, the server can and should gather the data it plans to insert/update on the server side.
you can add a validation function to the collection using the collection "allow" method to verify any updates from the client, in that case you don't need the Meteor.method and can just update from the client and validate it server-side.
Security (insert/update/delete operations) in meteor works in the same way as security in any other framework: before executing an action taken by the user, make sure the user has the rights to perform it. Security may appear as a weakness in Meteor, but it does not suffer from it any more than other frameworks (though, it's easier to exploit it in Meteor through the console).
The best way to solve it probably varies from case to case, but here's an example: if a user posts a post, the user should gain 5 points. Here's a bad way to solve it:
if(Meteor.isClient){
// Insert the post and increase points.
Posts.insert({userId: Meteor.userId(), post: "The post."})
Meteor.users.update(Meteor.userId(), {$inc: {'profile.points': 5}})
}
if(Meteor.isServer){
Posts.allow({
insert: function(userId, doc){
check(doc, {
_id: String,
userId: String,
post: String
})
// You must be yourself.
if(doc.userId != userId){
return false
}
return true
}
})
Meteor.users.allow({
update: function(userId, doc, fieldNames, modifier){
check(modifier, {
$inc: {
'profile.points': Number
}
})
if(modifier.$inc['profile.points'] != 5){
return false
}
return true
}
})
}
What makes it bad? The user can increase his points without posting a post. Here's a better solution:
if(Meteor.isClient){
// Insert the post and increase points.
Method.call('postAndIncrease', {userId: Meteor.userId(), post: "The post."})
}
if(Meteor.isServer){
Meteor.methods({
postAndIncrease: function(post){
check(post, {
userId: String,
post: String
})
// You must be yourself.
if(post.userId != this.userId){
return false
}
Posts.insert(post)
Meteor.users.update(this.userId, {$inc: {'profile.points': 5}})
}
})
}
Better, but still bad. Why? Because of the latency (the post is created on the server, not the client). Here's a better solution:
if(Meteor.isClient){
// Insert the post and increase points.
Posts.insert({userId: Meteor.userId(), post: "The post."})
}
if(Meteor.isServer){
Posts.allow({
insert: function(userId, doc){
check(doc, {
_id: String,
userId: String,
post: String
})
// You must be yourself.
if(doc.userId != userId){
return false
}
return true
}
})
Posts.find().observe({
added: function(post){
// When new posts are added, the user gain the points.
Meteor.users.update(post.userId, {$inc: {'profile.points': 5}})
}
})
}
The only disadvantage this solution suffers from is the latency of the increment of the points, but it is something we must live with (at least at the moment). Using observe on the server may also be a disadvantage, but I think you can get pass it by using the package collection hooks instead.

Meteor, get all users on a specific page

We are building a chat application and are currently working on a system to see all the users in a given room.
We have a Mongo Document set up with an array of active_users where we will push and pull user names to in order to keep track of the online users. We have come to the conclusion that realizing a user has connected to a given room is fairly simple. All we need to do is in the router, when a user accesses the page, we push that user's name into the document.
Now the tricky part is realizing when that user has left that given page? Obviously jQuery isn't a reliable option, so how do we know when a user's connection to a specific page is broken?
You could do this:
Meteor.publish("page", function() {
this._session.socket.on("close", function() {
//Change your active users here
});
});
and for your page that you track
Meteor.subscribe('page');
I use this in the analytics package on atmosphere
There's an Atmosphere package called Presence that does exactly what you need.
Some extra details from the README about keeping track of custom states...
State functions
If you want to track more than just users' online state, you can set a custom state function. (The default state function returns just 'online'):
// Setup the state function on the client
Presence.state = function() {
return {
online: true,
currentRoomId: Session.get('currentRoomId')
};
}
Now we can simply query the collection to find all other users that share the same currentRoomId
Presences.find({ state: { online: true, currentRoomId: Session.get('currentRoomId') } })
Of course, presence will call your function reactively, so everyone will know as soon as things change.
Meteor has connection hooks so you can run a function when the user disconnects from the server. Setting the onClose() callback inside a method called by the client will allow you to close the userId in the function.
Code on the server could be like this:
Meteor.methods({
joinRoom: function( roomId ){
var self = this;
Rooms.update( {roomId: roomId}, {$push:{userId: self.userId}});
self.connection.onClose( function(){
Rooms.update( {roomId: roomId}, {$pull:{userId: self.userId}})
});
}
});

How do I get the number of children in a protected Firebase collection?

I have a protected firebase collection for users of my site, just an array of user objects. The permission rules for users allow an authenticated user to access only their user object in the list of users and no one else.
I'm trying to setup a simple way to get the count of all users in the collection with this permission scheme so that I can display a total user count on my site, however there doesn't seem to be a way to get a count of all users without getting a permission problem.
Any ideas about how to fix this?
I suppose I could store a count at a publicly readable firebase location that gets incremented and decremented whenever a user is added/removed, but I'd rather not store the data twice and worry about mismatches.
I suppose I could also have an authenticated watcher on my server that bypasses the permission requirement and sends to the client (either through firebase by writing to public location or exposed as an api) a user count.
Ideally I'd like to have everything client side at the moment, so please let me know if there's a simple permissions based solution to this.
Thanks!
Data duplication is pretty much the norm in NoSQL, so storing a counter is perfectly reasonable. Check out the Firebase article on denormalization
This pretty much sums up the approaches as I understand them.
Using a counter
It's fast and it's fairly simple, assuming you're using good DRY principles and centralizing all your manipulations of the records. Utilize a transaction to update the counter each time a record is added or removed:
function addUser(user) {
// do your add stuff...
updateCounter(1);
}
function removeUser(user) {
// do your remove stuff...
updateCounter(-1);
}
function updateCounter(amt) {
userCounter.transaction(function(currentValue) {
currentValue || (currentValue === 0); // can be null
return currentValue + amt;
});
}
Separate public and secured data
Store sensitive data (email addresses, things people can't see) in a private path, keep their public user data readable.
This prevents the need to synchronize a counter. It does mean, however, that clients must download the entire list of public users to create a count. So keep the public profiles small (a name, a timestamp, not much else) so it works into the tens of thousands without taking seconds.
"users": {
".read": true,
"$user": {
// don't try to put a ".read" here; it won't remove access
// after the parent path allows it
}
}
"users_secured": {
"$user": {
".read": "auth.id === $user"
}
}
Utilize a server process
Easy and painless; uber fast for clients, easily handles hundreds of thousands of profiles as long as they have a small footprint. Requires you to maintain something. Heroku and Nodejitsu will host this for free until you have users coming out of your ears.
var Firebase = require('firebase');
var fb = new Firebase(process.env.FBURL);
fb.auth( process.env.SECRET, function() {
fb.child('users').on('value', function(snap) {
fb.child('user_counter').set( snap.numChildren() );
});
}

Resources