Prevent firebase function from overwriting existing data - firebase

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
//
})

Related

Firebase cross-service Security Rules not working in application

I'm trying to use the new Firebase cross-service Security Rules (https://firebase.blog/posts/2022/09/announcing-cross-service-security-rules) but I having some problems with Storage Rules accessing to Firestore data.
The problem seems to be with userIsCreator() function
match /certification/{certificationId}/{fileId} {
function userIsCreator() {
let certification = firestore.get(/databases/(default)/documents/certifications/$(certificationId));
return firestore.get(certification.data.creatorRef).id == request.auth.uid;
}
allow read, write: if userIsCreator()
}
The content of the Firestore Document is:
{
"data": {
othersValues,
"creatorRef": "/databases/%28default%29/documents/users/CuutSAtFkDX2F9T8hlT4pjMUByS2"
}
"id": "3EhQakDrsKxlacUjdibs"
"__name__":
"/databases/%28default%29/documents/certifications/3EhQakDrsKxlacUjdibs"
}
The creatorRef variable is a reference to a Firestore Document to user. Inside Users collection, the doc id is the UID of an user, so I'm obtaining the creatorRef of an item and then checking if the id of that user collection referenced is the same UID that user logged in.
The same function is working for Firestore Rules to avoid updating certification document if not the creator, without any problem.
It seems to be a problem calling to firestore.get to creatorRef after obtaining it but it not make sense!
Tested:
If I use Firestore Storage Rules validator, it is not failing and it says I have access to that resource from the UID typed in the tester (for other UID is failing as expected). But in my app, even logged in with creator user is getting permission error.
If changing the function to only one call directly to the Users collection id (return firestore.get(/databases/(default)/documents/users/CuutSAtFkDX2F9T8hlT4pjMUByS2).id == request.auth.uid;), it is working in the tester and my app. But it isn't a solution because I need to get first the Users collection ref for the creator!
For original function in the tester It's getting the variables as expected and returning true if simulate the creator UID! But for any reason, in the real app access it is getting unauthorized if making both calls!
Firebaser here!
It looks like you've found a bug in our implementation of cross-service rules. With that said, your example will create two reads against Firestore but it's possible to simplify this to avoid the second read.
Removing the second read
From your post:
return firestore.get(certification.data.creatorRef).id == request.auth.uid;
This line is a bit redundant; the id field is already contained in the certification.data.creatorRef path. Assuming you are indeed using Firestore document references, the format of creatorRef will be /projects/<your-project-id>/databases/(default)/documents/users/<some-user-id>. You can therefore update your function to the following:
function userIsCreator() {
let certification = firestore.get(/databases/(default)/documents/certifications/$(certification));
let creatorRef = certification.data.creatorRef;
// Make sure to replace <your-project-id> with your project's actual ID
return creatorRef ==
/projects/<your-project-id>/databases/(default)/documents/users/$(request.auth.uid);
}
I've tested this out in the emulator and in production and it works as expected. The benefit of doing it this way is you only have to read from Firestore once, plus it works around the bug you've discovered.

Firestore: Round-robin access to document in collection

I want to do something very simple, but not sure the best way to do this with Firestore.
I have an ads collection.
Each time an ad is accessed, I want to update the accessed property timestamp so I can just show the ad that hasn't been shown in the longest amount of time.
My security rules only allow users that carry a token with a payload of admin:true to create/modify ads.
So, from within the app, I can't update the timestamp each time an ad is accessed because the users aren't admins.
I looked at creating a function for this but realized that there is no onGet function that would allow me to do this (https://firebase.google.com/docs/functions/firestore-events)
I don't see anyway to allow a single property to be modified by any user.
What would be an appropriate way to do this with Firestore?
You could solve this either by creating a quite comprehensive rules validation where you make a check that all fields except accessed are unchanged. You can implement the admin role concept with custom claims as described in the answer on this post.
Checking that all fields except accessed are unchanged requires you to list and check all fields one by one.
service cloud.firestore {
match /databases/{database}/documents {
match /ads/{id} {
allow read: if true;
allow write: if request.auth.token.admin == true
|| (request.resource.data.someField == resource.data.someField
&& request.resource.data.anotherField == resource.data.anotherField);
}
}
}
Another way, you could do it is to create a callable cloud function that works similar to the Unix touch command. You simply call it from your client for every time your read an ad and you can safely update the accessed field on the post within that function.
export const touchAd = functions.https.onCall((data, context) => {
const adId = data.id;
return admin.firestore().collection('ads').doc(adId).update({
accessed: firebase.firestore.FieldValue.serverTimestamp(),
}));
});

Firebase security - Transactions

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.

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