1
votes

Following the post here I created a simple security rule and cloud function that gets called to see if a username already exists. The problem is that the security rule write check always passes and just sets the new value in that location (/username_lookup/user1).

When I try to write at this location using the realtime database rules simulator it works as expected, i.e. the write is blocked.

Can someone spot the problem?

The firebase security rule

"rules": {    
 "username_lookup": {
  "$username": {
     // not readable, cannot get a list of usernames!
     // can only write if this username is not already in the db
     ".write": "!data.exists()",

     // can only write my own uid into this index
     ".validate": "newData.val() === auth.uid"
  }
 }
}

And the cloud function

var fb = admin.database().ref();
createUser(uid, username);

function createUser(userId, usrname) {
    fb.child('username_lookup').child(usrname).set(userId, function(unerr) {
        if(unerr) { 
            res.setHeader('Content-Type', 'application/json');
            res.send(JSON.stringify({error: "the_error_code" }));
         }
     }); 
 }

Screenshot of the username_lookup object/index enter image description here

1

1 Answers

2
votes

You Cloud Functions access the Firebase database through:

var fb = admin.database().ref();

As you can see, the module is admin which indicates that you're using the Firebase Admin SDK. One of the key traits of the Firebase Admin SDK is:

Read and write Realtime Database data with full admin privileges.

source: https://firebase.google.com/docs/admin/setup

So the Admin SDK actually bypasses your security rules.

It's also a pretty bad practice to use an error handler for basic flow control.

Instead, use a Firebase transaction to read/write the location with the name in an atomic way:

fb.child('username_lookup').child(usrname).transaction(function(value) {
    if (value) {
        res.setHeader('Content-Type', 'application/json');
        res.send(JSON.stringify({error: "the_error_code" }));
        return; // abort the transaction
    }
    else {
        return userId;
    }
});