0
votes

I'm trying to secure an app in which users can manage their bands. The users can be part of multiple bands and therefore can have different roles depending on which band they are currently editing. The role is stored in a Map in the Band document.

Is there any way to apply this to firestore rules? So that the function getRole() will not only work for "bands" but also for other collections containing an idBand field (like "events")

My Setup looks like this: Firestore Setup

I tried to do something like this:

function getRole(){
    return get(/databases/{database}/documents/bands/request.resource.data.idBand/members/$(request.auth.uid)).data.role
}

match /bands/{document=**}  {
    allow read;
  allow write: if getRole() == 'OWNER';
}  
1

1 Answers

1
votes

Try something like this:

// Rules for a band document and its subcollections
match /bands/{bandID}{

  function isBandOwner(userID){ 
    // Determine if user is a band owner
    return get(/databases/$(database)/documents/bands/$(bandID)).data.members[userID].role == 'OWNER';
  }

  // Anyone can read
  allow read;

  // Only authenticated owners can write to band doc
  allow write: if request.auth != null && isBandOwner(request.auth.uid);

   // Rules for all of a band's subcollections
     match /{document=**}{

     // Anyone can read
     allow read;

     // Only authenticated owners can write to a band's subcollections
     allow write: if request.auth != null && isBandOwner(request.auth.uid);

   }        
}

To try it out, let's also restrict reads and try to read some test documents:

allow read: if isBandOwner(request.auth.uid);

Set up Firebase Auth with Google Sign-in and try to read from Cloud Firestore:

<script src="https://www.gstatic.com/firebasejs/5.5.9/firebase.js"></script>
<script>
  // Initialize Firebase
  // TODO: Replace with your project's customized code snippet
  var config = {
    apiKey: "<API_KEY>",
    authDomain: "<PROJECT_ID>.firebaseapp.com",
    databaseURL: "https://<DATABASE_NAME>.firebaseio.com",
    projectId: "<PROJECT_ID>",
    storageBucket: "<BUCKET>.appspot.com",
    messagingSenderId: "<SENDER_ID>",
  };
  firebase.initializeApp(config);

  //Sign in with Google
  var provider = new firebase.auth.GoogleAuthProvider();

  firebase.auth().signInWithPopup(provider).then(function(result) {
  // This gives you a Google Access Token. You can use it to access the Google API.
  var token = result.credential.accessToken;
  // The signed-in user info.
  var user = result.user;
  console.log(`UserId: ${user.uid}`)

  // Try reading Firestore docs
  var db = firebase.firestore();

    // Disable deprecated features
    db.settings({
      timestampsInSnapshots: true
    });

    // This doc has members.[my-uid].role = "OWNER"
    // Read should succeed
    db.doc("bands/abc").get().then((doc) => {
            console.log(`${doc.id} => ${doc.data().name}`);
    });

    // This doc has members.[my-uid].role = "BANDMEMBER"
    // Read should fail
    db.doc("bands/def").get().then((doc) => {
            console.log(`${doc.id} => ${doc.data().name}`);
    });
  // ...
}).catch(function(error) {
  // Handle Errors here.
  var errorCode = error.code;
  var errorMessage = error.message;
  // The email of the user's account used.
  var email = error.email;
  // The firebase.auth.AuthCredential type that was used.
  var credential = error.credential;
  // ...
  console.log('error login');
});
</script>