1

Having spent literally days trying the different, various recommended ways to do this, I've landed on what I think is the most simple and promising. Also thanks to the kind gents from this SO question: Get the index ID of an item in Firebase AngularFire

Curent setup

Users can log in with email and social networks, so when they create a record, it saves the userId as a sort of foreign key.

enter image description here

Good so far. But I want to create a rule so twitter2934392 cannot read facebook63203497's records.

Off to the security panel

enter image description here

Match the IDs on the backend

Unfortunately, the docs are inconsistent with the method from is firebase user id unique per provider (facebook, twitter, password) which suggest appending the social network to the ID. The docs expect you to create a different rule for each of the login method's ids. Why anyone using >1 login method would want to do that is beyond me.

enter image description here

(From: https://www.firebase.com/docs/security/rule-expressions/auth.html)

So I'll try to match the concatenated auth.provider with auth.id to the record in userId for the respective registry item.

According to the API, this should be as easy as

enter image description here

In my case using $registry instead of $user of course.

{
  "rules": {
    ".read": true,
    ".write": true,
    "registry": {
      "$registry": {
        ".read": "$registry == auth.id"
      }
    }
  }
}

But that won't work, because (see the first image above), AngularFire sets each record under an index value. In the image above, it's 0. Here's where things get complicated.

Also, I can't test anything in the simulator, as I cannot edit {some: 'json'} To even authenticate. The input box rejects any input.

enter image description here

My best guess is the following.

{
  "rules": {
    ".write": true,
    "registry": {
      "$registry": {
        ".read": "data.child('userId').val() == (auth.provider + auth.id)"
      }
    }
  }
}

Which both throws authentication errors and simultaneously grants full read access to all users. I'm losing my mind. What am I supposed to do here?

Community
  • 1
  • 1
Adam Grant
  • 12,477
  • 10
  • 58
  • 65
  • What authentication errors are you seeing? – hiattp Oct 20 '13 at 19:59
  • FIREBASE WARNING: set at /registry/ failed: permission_denied – Adam Grant Oct 20 '13 at 20:00
  • Can you share the code that is attempting the `set` command? Also, as Anonymous in the simulator can you attempt the same action as the `set` method that is failing? How do you know read access is being granted? – hiattp Oct 20 '13 at 20:26
  • I'm actually using AngularFire Implicit writing, so there isn't any set command, it just matches my array in $scope. – Adam Grant Oct 21 '13 at 19:11

2 Answers2

0

I don't think you want to store user-specific data under a non-user-specific index. Instead of push()ing to your firebase reference, store the user data behind a meaningful key.

e.g.

auth.createUser(email, password, function(error, user) {
  if (!error) {
    usersRef.child(user.id).set(stuff);
  }
});

Now you can actually fetch user data based on who is authenticated.

bennlich
  • 1,207
  • 10
  • 21
  • I'm actually not using the set command as I'm using implicit binding in AngularFire. I now have it set up with user keys as you recommend above. This has created a new problem here: http://stackoverflow.com/questions/19482108/prevent-items-in-scope-from-writing-to-a-different-users-records/19484097?noredirect=1#comment28901509_19484097 – Adam Grant Oct 21 '13 at 18:30
0

The custom Auth in the forge's simulator isn't the greatest but if you hit the tab key after selecting the input, it lets you paste or edit the field. At which point you can add {"provider":"facebook","id":"63203497"} or {"provider":"twitter","id":"2934392"} and hopefully get some useful debug out of it.

Assuming your firebase is something like:

{"registry":{
"0":{
    "id":"abbacadaba123",
    "index":"0",
    "name":"New Device",
    "userId":"facebook63203497"},
"1":{
    "id":"adaba123",
    "index":"1",
    "name":"Other Device",
    "userId":"twitter2934392"}

}
}

This may work for security rules:

{
    "rules": {
        "registry":{
            "$registryId":{
                ".read":"data.child('userId').val() === (auth.provider + auth.id)",
                ".write":"(data.child('userId').val() === (auth.provider + auth.id))||(auth != null && !data.exists())",
                ".validate": "newData.hasChildren(['id', 'index', 'name', 'userId'])",
                "id": {
                    ".validate":"newData.isString()"
                },
                "index": {
                    ".validate":"newData.isNumber()"
                },
                "name": {
                    ".validate":"newData.isString() && newData.val().length >= 1"
                },
                "userId": {
                    ".validate":"newData.val() === (auth.provider + auth.id)"
                }
            }
        }

    }
}

Your read rule tested as expected. The facebook user read-tests true on registry 0 and false on 1. The twitter user is false on 0 and true on 1. I did a couple quick tests on the .write and .validate rules and they seem to work.

Hope this helps at least rule out the firebase security rules portion of things, so you can focus on the AngularFire binding part.

Bro
  • 483
  • 5
  • 15