Search code examples
firebaserulesangularfirefirebase-securityfirebasesimplelogin

How to hide information in Firebase?


I have these requirements:

  1. User will log in via email and multiple Oauth providers. I use function like User.findByEmail('[email protected]'). So I need to have permision to see the list of users before being authenticated.
  2. User's email address, geolocation and age should be kept secretly from other users.

My first plan was:

users:{
  $user-id:{

//  public
    ".read" : true,
    name,
    created_at,

    private: {
        ".read" : "auth.email === data.child('email').val()",
        age,
        email,
        geolocation,
    }
    $piority = email
  }
}

Then I realized no, it just doesn't work that way. So would anyone please tell me how to do it correctly? Thanks in advance.

P.S. Firebase really needs a filter/serialize method.


Solution

  • There are really several questions in this post. The first is how to store private data. One simple change you can make is to invert the public/private and user keys--they don't have to be nested in the same user record.

    /users/$user_id/...public data...
    /private/$user_id/...private data...
    

    This makes securing the data quite a bit simpler.

    Another question is regarding duplicate email detection. If we assume you are using simple login here, this is all pretty moot. You can check to see if an email address exists by simply trying to create the account. An error will be returned if the email is already registered.

    If that doesn't solve it, you can still check this manually, without providing a list of users' emails. This is typically done with an index. When a new account is created, write something like this:

    /email_index/$escaped_email/$userid ($userid is the value)
    

    Now when you want to check if the email is available, you do a read like this:

    var ref = new Firebase(URL);
    function checkEmail(emailAddress, callback) {
       ref.child('email_index/'+escapeEmail(emailAddress)).once('value', function(snap) {
           callback(snap.val() !== null);
       });
    }
    
    function escapeEmail(emailAddress) {
       return (email || '').replace('.', ',');
    }
    
    checkEmail(EMAIL_ADDRESS, function(exists) {
       console.log(EMAIL_ADDRESS + (exists? 'does' : 'does not') + ' exist!');
     });
    

    To prevent someone from listing the emails, you do something like this in your (amazingly flexible and really quite sophisticated, even for enterprise apps) security rules:

    "email_index": {
       // no .read rule here means that the data cannot be listed; I have to know the email address to check it
       "$email_address": {
           ".read": true,
           // it can only be claimed once and the value must be my user id
           ".write": "auth.uid === newData.val() && !data.exists()"
       }
    }