0

I have a Firebase Realtime Database with the below structure.

notes
   noteId-1345
      access
         author: 1234567890
         members
             1234567890: 0 <--- Author
             0987654321: 1 <--- Member
      data
         title

When fetching data with the below code I only get "notes" I have access to.

database.ref('notes')
  .orderByChild(`access/members/${uid}`)
  .equalTo(0)
  .on('value', (snaps) => {

I wish to get "notes" where I am either author or member by using the code below. But this results in getting ALL notes, even those I am neither author or member in (?)

database.ref('notes')
  .orderByChild(`access/members/${uid}`)
  .endAt(99)
  .on('value', (snaps) => {

My database rules are:

"notes": {
  //Indexes
  ".indexOn": ["data/title", "access/author", "access/members"],
  
  //entry-level access
  ".read": "
    auth.uid !== null && query.endAt === 99
  ",
  "$note_id": {
    ".write": "        
            //Create new if authenticated
            !data.exists() && auth.uid !== null
            //Update if author or member
      ||(data.exists() && newData.exists() && data.child('access/members').child(auth.uid).exists())
            //Delete if author
            ||(data.exists() && !newData.exists() && data.child('access').child('author').val() === auth.uid)        
    ",      
    "data": {
      //access
      ".read": "
        //if author or assigned user
        data.parent().child('access').child('author').val() === auth.uid ||
        data.parent().child('access/members').child(auth.uid).exists()
      ",
      ".write": "
        //if author or assigned user
        data.parent().child('access').child('author').val() === auth.uid ||
        data.parent().child('access/members').child(auth.uid).exists()          
      "          
    },
    "access" : {
      //access
      ".read" : "
        (auth.uid !== null) &&
        (
          data.child('author').val() === auth.uid
        ||
          data.child('members').child(auth.uid).exists()
        )
      "
    }

This this expected bahaviour or am I doing something wrong?

Kind regards /K

UPDATE:

If changing my access rule to

  //entry-level access
  ".read": "auth.uid !== null && query.startAt === 0

and my code to

database.ref('notes')
  .orderByChild(`access/members/${uid}`)
  .startAt(0)
  .on('value', (snaps) => {

everything seems to be working!

I am a bit worried my access rules allow users to read all "notes", since the first code using .endAt(99) fetched ALL "notes".

How can I make sure users can ONLY read "notes" where they are listed as authors or members?

Kind regards /K

Kermit
  • 2,865
  • 3
  • 30
  • 53

1 Answers1

1

Your current data structure makes it easy to find the owner and members for a specific node. It does not however make it easy to find the notes for a specific user, which is what you're trying to do now.

As usual when using NoSQL databases, you can address this by expanding your data model. For example, to allow finding notes for a user, I'd keep an additional user_notes node in the root:

user_notes: {
  "$uid": {
    "noteId-1345": true
  }
}

So now whenever you associate a user with a note, you write that relation both in /notes/$noteid and in /user_notes/$uid. And you can then use this double data structure to perform lookups in both directions.

Also see:

Frank van Puffelen
  • 565,676
  • 79
  • 828
  • 807
  • Thank you for the prompt reply! Yes that is a good idea. I am not too keen to add extra nodes, if it can be avoided, though. :). Noticed that I can use .startAt(0) and keep my current structure. I am a but worried my access rules allows user to read ALL notes though. Have updated question. Kind regards /K – Kermit Jun 28 '20 at 17:46
  • 1
    My first linked answer explains why it can't be avoided. If you find a solution without the additional data structure, I'm sure others would love to learn about it too. :) – Frank van Puffelen Jun 28 '20 at 19:22
  • Thanks again for a great answer! :D Yes, I see now what you are saying. :D I cannot make it work securely because the read access needs to be set at the 'notes' level due to "database.ref('notes').orderByChild" and that level has no knowledge of the $note_id. So in reality anyone can read any node where the value of a key value pair is between 0-99! :/ I cannot prevent this since read and write rules cascade!! /K – Kermit Jun 29 '20 at 11:23