Search code examples
reactjsgoogle-cloud-firestorefirebase-security

rules for rbac using firebase


I'm trying to implement a role based access control using reactjs and firebase. My plan is to use the built-in authentication methods (email and password and google auth). The uuid will be saved in a collection called users.

I won't be using custom claims since I'll be storing a lot of data in users collection i.e profile picture, username, first name,last name etc.

So along with all this data I'm going to store another property called isAdmin which will be boolean value.

My users will be able to

  • register,
  • login
  • update profile
  • create posts
  • edit/delete own post
  • make post public/private
  • make collection of posts and make that collection private or public

My admin will be able to:

  • login
  • view user data
  • delete any user post/collection no matter it's visibility(private/public)
  • List item

Anyone not signed in will be able to:

  • View any public post or collection

This is the rules that I have written so far:

rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {
    match /users/{userId} {
      allow create
      allow read
      allow write: if request.auth.uid == userId || request.auth.token.isAdmin == true
    }
    match /posts/{postId}{
        allow read
    }
    match /collection/{collectionId}{
        allow read
    }
  }
}

I've no idea if I can make public/private using firebase and if any of my rule is correct. Need some insight.

Update 1: This is my re-written rules for firestore:

service cloud.firestore {
  match /databases/{database}/documents {
    function isPublic(){
        return resource.data.visibility == "public";
    }
    function isAdmin() {
      return request.auth.token.role == "admin";
    }

    function isUser(userId) {
      return request.auth.token.role == "user" && request.auth.uid == userId;
    }

    match /posts/{post} {
      allow write: if isAdmin() || isUser(userId); // only admins and users themselves can write
      allow read: if isPublic(); // any body can read if the visibility is public
    }
    match /users/{userId}{
        allow read: if true; // anyone can see a user
      allow write: if isAdmin() || isUser(userId); // only the user themselves or the admins can write
    }
  }
}

As mentioned above only the user who created a post will be able to write his/her own post so I guessed I'll need to pass the userId as well but the line: allow write: if isAdmin() || isUser(userId); is throwing in errors. How can I pass the postId and the userId at the same time to the match function?


Solution

  • This is what I came up with. Up until now everything works fine, I'll change the answer if I see any problem as soon as I created my frontend.

    I've also provided comments for better understanding:

    service cloud.firestore {
      match /databases/{database}/documents {
        // anyone can view the user
        // only the user who created the account and admin can edit accout
        // only the user who created the account and admin can delete accout
        match /users/{userId}{
          allow create: if true
          allow read: if true
          allow write: if request.auth.uid == userId // check for admin
          allow delete: if request.auth.uid == userId // check for admin
        }
        
        // anyone can view the link if it's public
        // only the author of the link and admin can edit/update/delete the link
        match /posts/{postId} {
          allow create: if request.auth.uid != null
          allow read: if true && request.resource.data.visibility == "public"
          allow write: if request.resource.data.author_id == request.auth.uid // check for admin
          allow delete: if request.resource.data.author_id == request.auth.uid // check for admin
        }
        
        // anyone can view the collection if it's public
        // only the author of the link and admin can edit/update/delete the collection
        match /collections/{collectionId}{
          allow create: if request.auth.uid != null
          allow read: if true && request.resource.data.visibility == "public"
          allow write: if request.resource.data.author_id == request.auth.uid // check for admin
          allow delete: if request.resource.data.author_id == request.auth.uid // check for admin
        }
      }
    }
    

    Update: After testing the rules work. I strongly advise anyone who want to take help from this answer create individual functions like isAdmin() and isOwner() etc. Clears a lot of the clutter