14

I'm struggling to set the proper security rules for my application.

An overview of the application I'm writing is that users can register themselves using email and password (I'm using Firebase Simple Login for this which works perfectly). Once logged in, user can add their todos.

angularFire('https://<firebase>/firebaseio.com/todos', $scope, 'todos');

And to add a new todo against any user, I simply update the todos model.

$scope.todos.push({
   user: 'a@b.com',
   todo: 'What to do?'
});

This security rules I'm using to restrict non-registered user to add any todo:

  {
    "rules": {
      ".read": true,
      "todos": {
        ".write": "auth != null",
        ".validate": "auth.email == newData.child('user').val()"
      }
    }
  }

But it does not allow even an authenticated user to write any data and throwing an error, "FIREBASE WARNING: on() or once() for /todos failed: Error: permission_denied."

But If I add the following data in simulator then it works as expected.

{user: "a@b.com", todo: 'What to do?'} 

Here is the log:

/todos:.write: "auth != null"
    => true
/todos:.validate: "auth.email == newData.child('user').val()"
    => true
/todos:.validate: "auth.email == newData.child('user').val()"
    => true

Write was allowed.
codef0rmer
  • 10,284
  • 9
  • 53
  • 76
  • You can try using the security simulator in Forge to see why this isn't working. Two comments: 1) You seem to be writing an array with one object, it should probably be just one object. 2) The "$todos" variable should probably be just "todos". – Anant Sep 07 '13 at 19:18
  • @Anant: Thx for the reply. As you said, I removed the $ and changed validate rule with `".validate": "auth.email == 'a@b.com'"` where `a@b.com` is a registered user - this works but not `".validate": "'a@b.com' == newData.child('user').val()"`. Is it possible to see what newData has? – codef0rmer Sep 07 '13 at 20:10
  • @Anant: I've updated the question to give you more clarity. As you said, $ is not needed then why its used everywhere in the documentation? – codef0rmer Sep 08 '13 at 06:33
  • There could be a timing issue at play - i.e. you're trying to push data before authentication is complete. Are you using `angularFireAuth` to do authentication, or doing it manually via `FirebaseSimpleLogin`? – Anant Sep 09 '13 at 18:06
  • "$" is used in the documentation as a variable, i.e. when you don't know what the value of that node will be. In your cases, "todos" is a fixed constant, so you don't need the "$", but you do need a child element like "$todoid", or just "$id" to refer to the auto-generated push objects. – Anant Sep 09 '13 at 18:07

1 Answers1

17

push adds a new child with a randomly generated ID (in chronological order) to /todos. So, newData isn't pointing to what you think it is pointing to. Change your rules to:

{
  "rules": {
    ".read": true,
    "todos": {
      "$todoid": {
        ".write": "auth != null",
        ".validate": "auth.email == newData.child('user').val()"
      }
    }
  }
}

Update: Above rule is valid but angularFire currently writes the whole array back to the server causing the auth to fail. You can use angularFireCollection instead, to only write the new TODO back, like so:

$scope.todos = angularFireCollection(new Firebase(URL));

$scope.todos.add({user: 'a@b.com', todo: 'What to do?'});

There's an open issue to optimize angularFire's behavior when new items are added to the list, but in the meantime you can use angularFireCollection to get the right behavior.

codef0rmer
  • 10,284
  • 9
  • 53
  • 76
Anant
  • 7,408
  • 1
  • 30
  • 30
  • 1
    The issue is in angularFire, posted a workaround to the firebase-talk mailing list (use angularFireCollection.add instead). – Anant Sep 10 '13 at 18:37