0
votes

My current firebase structure looks like the following

firebase

Originally, my security looks like

{
    "rules": {
        "users": {
            "$companyId": {
                "$userId": {
                    ".read": "auth != null",
                    ".write": "auth != null",
                }
            }
        }
    }
}

This is all fine as I can simply get the user detail via

// get user information from database
getUserInformation(companyId: string, uid: string) {
    let path = '/users/' + companyId + '/' + uid;
    return this.af.database.object(path, { preserveSnapshot: true })
}

However, I want to add more security to this branch such that the user can not update the code in the front end to modify some of these entries via write operation (Read is OK). I want to have the user the ability to modify these entries based on the isAdmin flag For example,

  1. companyId (read: auth != null, write: isAdmin == true)
  2. companyName (read: auth != null, write: isAdmin == true)
  3. dailySubmissionLimit (read: auth != null, write: isAdmin == true)
  4. defaultApproverEmail (read: auth != null, write: isAdmin == true)
  5. email (read: auth != null, write: isAdmin == true)
  6. firstName (read: auth != null, write: isAdmin == true)
  7. id (read: auth != null, write: isAdmin == true)
  8. isAccountActive (read: auth != null, write: isAdmin == true)
  9. isAdmin (read: auth != null, write: false)
  10. isPasswordOutdated (read: auth != null, write: auth!=null)
  11. lastLoggedIn (read: auth != null, write: auth!=null)
  12. lastName (read: auth != null, write: isAdmin == true)
  13. passwordChangeDate_epoch (read: auth != null, write: auth!=null)
  14. registerationDate (read: auth != null, write: isAdmin == true)
  15. team (read: auth != null, write: isAdmin == true)
  16. userDefaults (read: auth != null, write: auth!=null)

Since all read = auth != null.. Initially, I thought since I am able to read all the node, I could set the security individual rule for each node as shown below

{
    "rules": {
        "users": {
            "$companyId": {
                "$userId": {
                    "companyId": {
                        ".read": "auth != null",
                        ".write": "(auth != null) && (root.child('users').child($companyId).child(auth.uid).child('isAdmin').val() == true)"
                    },
                    "isAdmin": {
                        ".read": "auth != null",
                        ".write": "false"
                    },
                    "lastLoggedIn": {
                        ".read": "auth != null",
                        ".write": "auth != null"
                    },
                    ...
                }
            }
        }
    }
}

This however failed when I try to getUserInformation() because I am querying '/users/' + companyId + '/' + uid and the security rule for that particular branch is unknown

I am guessing as an alternative, I could query each node instead '/users/' + companyId + '/' + uid + '/companyId' and combine the data using forkJoin or some sort. This would be highly ineffecient as I am now calling the database nth time instead of one time.

Another approach that I can think of is to structure the data abit differently by categorising them into three branches "userCanModify", "adminCanModify", "noOnCanModify". Then set security rule for these three nodes as required. But this seems more like a hack to me as I have to re-structure my data to a very odd format. Also, I will still have to make three request to get the full picture of the user detail

{
    "rules": {
        "users": {
            "$companyId": {
                "$userId": {
                    "userCanModify": {
                        "lastLoggedIn": {
                            ".read": "auth != null",
                            ".write": "auth != null"
                        },
                        ...
                    }, 
                    "adminCanModify": {
                        "companyId": {
                            ".read": "auth != null",
                            ".write": "(auth != null) && (root.child('users').child($companyId).child(auth.uid).child('isAdmin').val() == true)"
                        }, 
                        "companyName": {
                            ".read": "auth != null",
                            ".write": "(auth != null) && (root.child('users').child($companyId).child(auth.uid).child('isAdmin').val() == true)"
                        },
                        ...
                    }, 
                    "noOneCanModify": {
                        "isAdmin": {
                            "read": true, 
                            "write": false
                        },
                        ...
                    }
                }
            }
        }
    }
}

Is there a much better solution out there that I am missing?

1

1 Answers

1
votes

Did you tried this security rule syntax? :

 {
    "rules": {
        "users": {
            "$companyId": {
                "$userId": {
                    "$companyIdbis": {
                        ".read": "auth != null",
                        ".write": "root.child('users').child($companyId).child($userId) === auth.uid &&  data.child('isAdmin').exist()"
                    },

                    "isAdmin": {
                        ".read": "auth != null",
                        ".write": "false"
                    },
                    "lastLoggedIn": {
                        ".read": "auth != null",
                        ".write": "auth != null"
                    },
                    ...
                }
            }
        }
    }
}

(If I'm not wrong :

data.child('isAdmin').exist()"

should be the equivalent of :

  `root.child('users').child($companyId).child($userId).child($companyIdbis).child('isAdmin').exist()"`

[EDIT]: But I recommend you to have a "flat structure" by separating the Nodes Users and Companies Likes :

Users
    userId
        compagnyId : true
        etc ...
Compagnies
    compagnyId
        etc ...

This allow you te retrieve a List of users without having to browse all the compagnies nodes