how can I ensure atomic writes of multiple document writes on client side only?
for example, when creating a record, a record document is generated and concurrently, a record log document is also generated (two different locations).
I wouldn't want the user to override by creating only the record document without the record log document. is this possible with the current firestore security rules?
Use the getAfter() function to look at what the state of a document would be after a write or set of writes. You can use this to make sure a value in another document is updated, for example:
service cloud.firestore {
match /databases/{database}/documents {
// Allow a user to update a record or log only if they keep the timestamps equal for the same ID
match /records/{record} {
allow write: if request.auth.uid != null &&
getAfter(/databases/$(database)/documents/logs/$(record)).data.timestamp == request.resource.data.timestamp;
}
match /logs/{log} {
allow write: if request.auth.uid != null &&
getAfter(/databases/$(database)/documents/records/$(log)).data.timestamp == request.resource.data.timestamp;
}
}
Docs: https://firebase.google.com/docs/reference/rules/rules.firestore.html#.getAfter
Related
I have the following firestore setup, how can I set the rules to let only owners of marketCompra, marketTroca and marketVenda documents to delete?
The other ones I will let everybody see.
Firestore does not have a built-in concept of a "document owner". For that, your app will need to include data indicating which user "owns" the document (for example having a ownerUid field set to the Firebase Authentication UID of the user that created/owns the document) and then in your Firestore Rules you can ensure that the allow delete: condition checks the current request.auth.uid to that field's value.
service cloud.firestore {
match /databases/{database}/documents {
match /marketCompra/{docId} {
allow delete: if isOwner();
}
function isOwner() {
return request.auth.uid == request.resource.data.ownerUid;
}
}
}
For "The other ones I will let everybody see", you can simply use:
allow read: if request.auth.uid != null
meaning that as long as the user is logged in to the app, they can issue a read request against this collection (or document).
By the way, you might find this article worthy of a review: https://fireship.io/snippets/firestore-rules-recipes/
I am developing a small webapp based on a firestore database and I just want certain users to access (read and write) the data. It may be like 15 or 20 UIDs. I have created a collection -in root level- named "whitelist" and inside it each document has the UID of the user and several fields with the userID, userName and userEmail.
Then I have written my rules as follow:
service cloud.firestore { match /databases/{database}/documents {
match /{document=**} {
allow read, write: if request.auth != null && data.child('whitelist').hasChild(auth.uid);
} } }
However, I have tried with my own user to see if it works and the user is not able to read anything at all even though there is a document within the whitelist collection. What am I doing wrong?
// This will check if the user is authenticated and his UID exists
// in "haveAccess" collection which must be placed in root. You can use the same for ".write"
".read":"(auth !== null && data.child('haveAccess').hasChild(auth.uid))"
You can check the documentation for rules here : Google Firebase Database Rules
It is well worth to ensure that the statement data.child('whitelist').hasChild(auth.uid) returns true.
Also according to Data validation you should use resource.data
The resource variable refers to the requested document, and resource.data is a map of all of the fields and values stored in the document. For more information on the resource variable, see the reference documentation.
You can try adding resource:
service cloud.firestore { match /databases/{database}/documents {
match /{document=**} {
allow read, write: if request.auth != null && resource.data.child('whitelist').hasChild(auth.uid);
} } }
I'm trying to write some Firestore security rules that only allow users to write to certain fields in their documents (e.g. email, gender, preferredName, address).
I wrote the following write rule to restrict access to specific fields:
match /databases/{database}/documents {
match /users/{userId} {
allow read: if userIsAuthenticated()
&& userIsAccessingTheirData(userId);
// Users can always write to specific fields
allow write: if userIsAuthenticated()
&& userIsAccessingTheirData(userId)
&& request.resource.data.keys().hasOnly(["preferredName","gender", "email", "address"]);
The rules works well for when we call userDoc.set in code, but it doesn't work when we call userDoc.update.
Using the Firestore rules emulator, I can see that when we call "set" the request.resource.data.keys() only has the fields that are being passed in the call, but when I call "update" all the fields of the document are in the key collection :-( which makes it impossible to filter.
Is there a way to write a security rule that restricts the fields like above that works for both set and update?
The request.resource variable represents the document as it will exist after the operation succeeds (if it succeeds of course). So request.resource does not just contain the fields that are being updated, but also the other values from the existing document.
It's always been possible to check if a field is being updated by comparing request.resource.data.fieldname with resource.data.fieldname.
But recently a new affectedKeys() function was introduced to security rules that shows just the delta:
// This rule only allows updates where "a" is the only field affected
allow update: if request.resource.data.diff(resource.data).affectedKeys().hasOnly(["a"]);
Also see the release notes for Firebase security rules.
you can access document fields using request.resource.data.{field}
for example if you want to restrict updating dob :
service cloud.firestore {
match /databases/{database}/documents {
// Make sure all cities have a positive population and
// the name is not changed
match /users/{user} {
allow update: if request.resource.data.dob == resource.data.dob;
}
}
}
This means that the document can be updated as long as dob hasn't changed which is what we are trying to achieve.
I am trying to only allow reads to some documents if the user's uid matches the document's uid.
resource.data.uid == request.auth.uid
The security simulator gives me this errror for this line.
Error: simulator.rules line [20], column [16]. Null value error.
Is this a functionality that is just not supported by Firestore security rules?
If I understood well, I think you want something like this:
service cloud.firestore {
match /databases/{database}/documents {
match /{document=**} {
allow list, create: if request.auth.uid != null;
allow get, update, delete: if request.auth.uid != null && resource.id == request.auth.token.email;
}
}
}
This code should allow users to create their own documents and access/edit only them.
Also, list allowance is required to check if the document exists before creating it, so apparently it needs to be allowed to all authenticated users.
Note that resource.id is actually the document's name. So it means that when you create a document, its name must be the user uid. And of course, you won't be able to have more than one document per collection for the same user.
I have some trouble with setting up my rules for a firestore project. I try to learn the database setup but can't find any solution for this. So there's no problems when i try to get a document from my collection "lists". But when i try to get all of the documents in the collection "lists" xcode tells me "Missing or insufficient permissions".
My goal is to have users that are able to create documents in collection "lists" but they can only read the documents in "lists" where they appear in the document array "members".
Right now I can add documents in collection("lists") without any problem but I can't read them. I can only read them one by one from xcode with a specific target.
Any tips or ideas?
service cloud.firestore {
match /databases/{database}/documents {
match /{documentId} {
allow read: if request.auth.uid != null;
}
match /lists/{docId} {
allow write: if request.auth.uid != null;
allow read: if request.auth.uid in resource.data.members
}
}
}
Xcode.
//working
let docRef = db.collection("lists").document("j0hHA5TLPETf6JRMbC1s")
docRef.getDocument { ...
//not working due to permission failed
db.collection("lists").getDocuments() { ...
Your rule doesn't work because it assuming that the rule will filter out all the documents that don't match the rule. This is not how security rules work. Security rules are not filters. From the linked documentation:
When writing queries to retrieve documents, keep in mind that security
rules are not filters—queries are all or nothing. To save you time and
resources, Cloud Firestore evaluates a query against its potential
result set instead of the actual field values for all of your
documents. If a query could potentially return documents that the
client does not have permission to read, the entire request fails.
The client must only request documents that would satisfy the rules. Right now, the query is asking for ALL documents in the lists collection, regardless of whether or not the client has access to them. This is going to fail the security rule, because it's attempting to read documents that it doesn't have access to.
What you need to do instead is make your query only request documents that are readable by the user. This means that you should probably be using an array-contains filter on the client to match only documents that the rule would allow it to read.
Actually, you are on the right path, I think if you change your code like this, it will work.
Instead of this:
match /{documentId} {
allow read: if request.auth.uid != null;
}
Use this:
match /lists {
allow read: if request.auth.uid != null;
}
So when you try to access lists without documentId, it will check the auth.uid.
But when you try to access a document ex. lists/1, it will check whether that user exists in the array.