Search code examples
firebasegoogle-cloud-firestorefirebase-authenticationfirebase-security

how to write auth based Firestore Rules for specific collections


I have a collection called Orders and I have two other collections for Users & Drivers. I want the Orders collection to be accessible only by the UID's that inside the Drivers collection.

All the people signed into the app are users, but Drivers are the only users that can access the Orders documents.

The logic is to check if the user is signed and the Drivers collection contains a field that has the UID of that user. If the collection field contains the UID of that user, then he can access that collection of document.

allow write, read: if isSignIn() & `Driver colletion contains that user UID`

How can I do that and if I can I need some help write that code?

here is how the Database look like

here are the Orders collections

Here are my rules and I did not change that much in it. All that I know is this code is for every document in my database, so I need to write some specifics

rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {
    match /{document=**} {
      allow write, read: if isSignIn();
    }
    
    function isSignIn(){
    return request.auth != null;
    }
    
  }
}

here is how I'm getting the data from Firestore

func newOrders(){
        
        let fireStore = Firestore.firestore()
        let doc = fireStore.collection("Orders")
        
        self.driverListener = doc.addSnapshotListener { (query, err) in
            if err != nil {
                print(err?.localizedDescription ?? "")
            }
            guard let querysnap = query else {return}
            
            querysnap.documentChanges.forEach({ change in
                
                if change.type == .added {
                    
                self.DriverOffers = []
                for document in querysnap.documents{
                        
                    let snap = document.data()

                guard let orderLoc = snap["orderLocation"] as? GeoPoint else {return}
                    
                guard let docId = snap["docId"] as? String else {return}
                guard let name = snap["name"] as? String else {return}
                guard let phone = snap["phone"] as? String else {return}
                guard let time = snap["time"] as? String else {return}
                guard let marketName = snap["marketName"] as? String else {return}
                guard let price = snap["amount"] as? String else {return}
                guard let userImg = snap["userImg"] as? String else {return}
                guard let storeImg = snap["storeImg"] as? String else {return}
                guard let order = snap["order"] as? String else {return}
                guard let userid = snap["userUid"] as? String else {return}
                guard let timestamp = snap["date"] as? Timestamp else {return}
                let date = Date(timeIntervalSince1970: TimeInterval(timestamp.seconds))

                guard let userlocation = snap["userLocation"] as? GeoPoint else {return}

                    let offer = driverOrdersData(docId: docId, userUid: userid, name: name, phone: phone, amount: price, time: time, marketName: marketName, storeimgUrl: storeImg, userImgUrl: userImg, date: date, orderDetails: order, orderLocation: orderLoc, userLocation: userlocation, distance1: distance1, distance2: distance2 )
                    
                    self.DriverOffers.append(offer)
                    DispatchQueue.main.async {
                    self.DriverOrdersTV.reloadData()

                    }
                    }
                
            }
            }
        })
        
        }

}

Solution

  • You cannot check in security rules whether a document exists with a specific value in it, as that would require your rules to query the collection and would be costly and not scale.

    The trick to implement this use-case (and many others) is to use the UID of a user for the document ID in the Drivers (and probably also Users) collection. You can do this by adding those documents with something like

    let uid = ....; // wherever you get the UID from
    firebase.firestore().collection("Drivers").doc(uid).set({ uid: uid });
    

    Now with that structure in place, you can check whether a document with the specific UID exists in your security rules, which is possible:

    function isDriver() {
      return exists(/databases/$(database)/documents/Drivers/$(request.auth.uid))
    }
    

    Also see the Firebase documentation on accessing other documents in security rules.