Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Firebase Database - security consideration in an inverse index

In the Firebase guides, one of the recommendations is to maintain an inverse index to keep track of user actions. Here's a snippet of what I'm referring to:

// An index to track Ada's memberships
{
  "users": {
    "alovelace": {
      "name": "Ada Lovelace",
      // Index Ada's groups in her profile
      "groups": {
         // the value here doesn't matter, just that the key exists
         "techpioneers": true,
         "womentechmakers": true
      }
    },
    ...
  },
  "groups": {
    "techpioneers": {
     "name": "Historical Tech Pioneers",
     "members": {
        "alovelace": true,
        "ghopper": true,
        "eclarke": true
      }
    },
    ...
  }
}

Each user keeps track of his/her groups in an inverse index - meaning in this case, that the keys hold the real value, and the value doesn't matter.


Update

I wasn't sure how to update the index technically but I got it after a little research: the setValue can take all manor of variables, not just key-value pairs. That means that updating an index is pretty simple: just get a reference to groups/$group_id/members/$member_id and set its value to true.

Now my question is different:

Lets say all groups are private. Meaning users can join a group by invitation only - a current group member must add another user to the member list. So if I'm ghopper and I want to add alovelace as a member, I need to update her index which is part of her user object - which means I have to know her user ID somehow and have write access to her groups field - and that seems like a security risk.

Any thoughts on how to manage this while keeping access as restricted as possible? Perhaps another DB object that maps a user known identifier, like an email to a group list?

like image 830
Itai Hanski Avatar asked Nov 09 '22 14:11

Itai Hanski


1 Answers

Solution 1 - Client-side

One solution would be to have a separate user invitation object so that ghopper can add alovelace to the private group and have that show up alovelace's invitations instead of automatically adding her to the group. alovelace would then need to approve the addition and update her group membership. This way, only the user retains access to their user record. This is quite similar to adding friends on facebook, or requesting connections on linkedin.

For illustration, the schema could look something like this

// An index to track Ada's memberships
{
  "users": {
    "alovelace": {
      "name": "Ada Lovelace",
      // Index Ada's groups in her profile
      "groups": {
         // the value here doesn't matter, just that the key exists
         // Only Ada can write here
         "techpioneers": true,
         "womentechmakers": true
      }
    },
    ...
  },
  "invitations": {
    "alovelace": {
      "name": "Ada Lovelace",
      "groups": {
         // the value here doesn't matter, just that the key exists
         // Anyone can write here
         "ghoppersfanclub": true, // Ada might accept this and move it to groups
         "explicitcontentgroup": true, // Ada might reject this and delete this entry
      }
    },
    ...
  },
  "groups": {
    "techpioneers": {
     "name": "Historical Tech Pioneers",
     "members": {
        "alovelace": true,
        "ghopper": true,
        "eclarke": true
      }
    },
    ...
  }
}

Solution 2 - Server-side

Although Firebase is meant to enable building apps without server code, there are instances where you should get the server in the mix. In my opinon, security and the execution of trusted actions like one user making changes to another user's record (if we're not using a separate object like 'invitations' above) should be handled by your trusted server using the admin API. When ghopper adds alovelace as a member, one possible event sequence would be:

  • Check that ghopper belongs to the group and can add another user (client-side)
  • Send a request to your server with payload that includes group name/id, user sending the request and email of the user being added

  • Server then looks up alovelace's user id using the provided email and update the user record.

    admin.auth().getUserByEmail(alovelace_email)
      .then(function(userRecord) {
        // Add group to alovelace's groups.
        // Trigger a client-side notification using child_changed
        // Allow alovelace to approve or decline addition to group
      })
      .catch(function(error) {
        console.log("Error fetching user data:", error);
      });
    

The above example uses email as a public/shareable unique identifier, but there's also a similar getUserByPhoneNumber(phoneNumber) method.

like image 55
WittyID Avatar answered Nov 15 '22 07:11

WittyID