title: Restricting child/field access with security rules
body: <p>I'm writing an app that allows users to submit nominations which are moderated before being displayed to other users. This requires a number of restrictions I've so far been unsuccessful in implementing with security rules:</p>
  
  <ol>
  <li>Hide any nominations that haven't been approved yet</li>
  <li>Hide private fields from submission (phone, approval status, creation date etc.)</li>
  </ol>
  
  <p>My current rules are as follows:</p>
  
  <pre><code>{
      "rules": {
          "nominations": {
              ".read": true,
  
              "$nominationId": {
                  ".read": "data.child('state').val() == 'approved' || auth != null", // Only read approved nominations if not authenticated
                  ".write": "!data.exists()", // Only allow new nominations to be created
  
                  "phone": {
                      ".read": "auth != null" // Only allow authenticated users to read phone number
                  },
  
                  "state": {
                      ".read": "auth != null", // Only allow authenticated users to read approval state
                      ".write": "auth != null" // Only allow authenticated users to change state
                  }
              }
          }
      }
  }
  </code></pre>
  
  <p>Child rules (e.g. <code>$nomination</code>) don't prevent the entire child from being read from the parent. If I listen for <code>child_added</code> on <a href="https://my.firebaseio.com/nominations" rel="noreferrer">https://my.firebaseio.com/nominations</a> it happily returns all children and all their data even with the above security rules in place.</p>
  
  <p>My current workaround idea for this is to keep a separate node named <code>approved</code> and simply move the data between lists whenever someone approves or rejects a nomination, but it seems like a horribly broken approach.</p>
  
  <p><strong>Update</strong></p>
  
  <p>Following <a href="https://stackoverflow.com/questions/14296625/restricting-child-field-access-with-security-rules/14297061#14297061">Michael Lehenbauer</a>'s excellent comment I've reimplemented the initial idea with minimal effort.</p>
  
  <p>The new data structure is as follows:</p>
  
  <pre><code>my-firebase
      |
      `- nominations
          |
          `- entries
          |   |
          |   `- private
          |   `- public
          |
          `- status
              |
              `- pending
              `- approved
              `- rejected
  </code></pre>
  
  <p>Each nomination is stored under <code>entries</code> with private data such as phone number, e-mail etc. under <code>private</code> and publicly viewable data under <code>public</code>.</p>
  
  <p>The updated rules are as follows:</p>
  
  <pre class="lang-js prettyprint-override"><code>{
      "rules": {
          "nominations": {
              "entries": {
                  "$id": {
                      ".write": "!data.exists()",
  
                      "public": {
                          ".read": true,
                      },
  
                      "private": {
                          ".read": "auth != null"
                      }
                  }
              },
  
              "status": {
                  "pending": {
                      ".read": "auth != null",
  
                      "$id": {
                          ".write": "root.child('nominations/entries').child($id).exists() &amp;&amp; (auth != null || newData.val() == true)"
                      }
                  },
  
                  "approved": {
                      ".read": true,
  
                      "$id": {
                          ".write": "root.child('nominations/entries').child($id).exists() &amp;&amp; auth != null"
                      }
                  },
  
  
                  "rejected": {
                      ".read": "auth != null",
  
                      "$id": {
                          ".write": "root.child('nominations/entries').child($id).exists() &amp;&amp; auth != null"
                      }
                  }
              }
          }
      }
  }
  </code></pre>
  
  <p>And the JavaScript implementation:</p>
  
  <pre class="lang-js prettyprint-override"><code>var db = new Firebase('https://my.firebaseio.com')
  var nominations = db.child('nominations')
  
  var entries = nominations.child('entries')
  
  var status = nominations.child('status')
  var pending = status.child('pending')
  var approved = status.child('approved')
  var rejected = status.child('rejected')
  
  // Create nomination via form input (not shown)
  var createNomination = function() {
      var data = {
          public: {
              name: 'Foo',
              age: 20
          },
  
          private: {
              createdAt: new Date().getTime(),
              phone: 123456
          }
      }
  
      var nomination = entries.push()
      nomination.setWithPriority(data, data.private.createdAt)
  
      pending.child(nomination.name()).set(true)    
  }
  
  // Retrieve current nomination status
  var getStatus = function(id, callback) {
      approved.child(id).once('value', function(snapshot) {
          if (snapshot.val()) {
              callback(id, 'approved')
          } else {
              rejected.child(id).once('value', function(snapshot) {
                  callback(id, snapshot.val() ? 'rejected' : 'pending')
              })
          }
      })
  }
  
  // Change status of nomination
  var changeStatus = function(id, from, to) {
      status.child(from).child(id).remove()
      status.child(to).child(id).set(true)
  }
  </code></pre>
  
  <p>The only part of the implementation I'm struggling with is handling status changes, my current approach can surely be improved upon:</p>
  
  <pre class="lang-js prettyprint-override"><code>_.each([pending, approved, rejected], function(status) {
      status.on('child_added', function(snapshot) {
          $('#' + snapshot.name()).removeClass('pending approved rejected').addClass(status.name())
      })
  })
  </code></pre>
  
  <p>I was planning on using <code>child_changed</code> on <code>nominations/status</code> but I haven't been able to get it working reliably.</p>