Firestore security rule that only allows empty documents - firebase

I'm basically trying to use a firestore collection as a an email list. Anyone can create a document that has their email as the id and nothing more. The tricky part is the "and nothing more" bit. When no data is provided in the request, request.resource is undefined which you can't check for in security rules to my knowledge. Is this possible? Or is it necessary to have something like one mandatory field for this use case?

Having empty documents regularly leads to issues down the line. Why not require a single marker field, and validate that in rules?
request.resource.data.keys.hasOnly("marker")

For the benefit of others looking to make an email list in firestore, this is the full rule I ended up using:
match /email-list/{email} {
allow get: if true;
allow list: if false;
allow create: if request.resource.data.keys().hasOnly(["marker"])
&& request.resource.data.marker == true
}

Related

Firestore and Rules | Allowing/disallowing read access to post under a public/private user?

Situation
I have the following Firestore setup
/posts/{id}
/posts/{id}/comments/{id}
/users/{id}/followers/{userId}
A user profile can either be public or private. All users can see posts by public users, but only users who follow private users can see said post, ie. they are in the owner's followers collection.
Current Solution
The post doc looks like this:
owner_account_visibility: public || private
ownerId: uid
The comment doc looks the same:
owner_account_visibility: public || private
ownerId: uid
My rules look like this
match /events/{eventId} {
allow read: isValid();
match /eventComments/{commentId} {
allow read: isValid();
}
}
function isValid(){
return (resource.data.owner_account_visibility == "public" || exists(/users/$(resource.data.ownerId)/followers/request.auth.uid)))
}
Problem
I see problems/questions with this solution:
Problem: A user may create many posts, which in turn may have lots of comments. This means that if a user updates their account visibility, a cloud function has to update possibly thousands of post and comment documents
Problem: A user may load many private posts and comments, and for each one of those is a database read, which can get very expensive as the user scrolls their feed
Question: In the isValid() function, there are two conditions seperated by an OR sign (||). Does this mean that if the first condition returns true (resource.data.owner_account_visibility == "public") then the function will not check the second condition (exists(/users/$(resource.data.ownerId)/followers/request.auth.uid)), saving me a database read? If this isn't the case, then I will waste a loooot of reads when a user loads tons of comments from a post even though it is public...
Does anyone have a proposed solution to this problem? Any help would be appreciated :)
I solved this myself. In short, instead of letting a user set their accounts' visibility, I let them set each post's visibility. This is simply because that is the functionality I want in my app. Now, I can simply use resource.data.post_visibility == "public", avoiding the issue of having to update every post if a user changes their account's visibility. If the first condition is false, I do as I did in my current solution in the question (exists(/users/$(resource.data.ownerId)/followers/request.auth.uid)). Also, comments and replies to a post are opened to all authenticated users even though the post is set to private, since comments aren't necessarily the post owner's own content/sensible information

Firestore Security: How to create rules based on a changed property which identifiers a document

Imagine a User Document which holds a property "restaurants" witch again holds keys for all restaurant he is responsible for.
I want to implement a security rule which only allows updating the property "restaurants" if the changed restaurant-id in the update, references a restaurant owned by the user.
Therefore in security rules i want to detect the changed-restaurant-id, load corresponding restaurant-doc and check if the field "owner" inside the restaurant-doc is equal the user-id.
I was already able to implement a rule to check only 1 Id was modified by implmenting something like this:
function hasSingleChange(){
let changedKeys = request.resource.data.diff(resource.data).affectedKeys()
return changedKeys.size() == 0;
}
Now I wanted to get the the specific Id that changed and use it for building the query for the restaurant:
function getChangedKeys() {
let changedKeys = request.resource.data.diff(resource.data).affectedKeys()
return changedKeys[0]
}
But unfortunately "return changedKeys[0]" will not work as affectedKeys returns a Set with limited operations available.
Is there an other way around to load a document in security-rules, based on changed property to be able to apply checks on this changed, referenced document?
Edit
Let me explain you what i actually want to solve with this.
I have a Domain consisting of graphs, nodes and users as shown here:
What I want to accomplish is that users can access read/update/delete a node if the node is in a at least one graph which the user is the owner for. In the example above the user should have acess to node "n1" but not to node "n2".
My idea was to check the permission on the node as following:
match /nodes/{nodeId} {
allow read: if resource.data.graphs.keys().hasAny(get(/databases/$(database)/documents/users/$(request.auth.uid)).data.graphs.keys());
I guess this would work (?). But I need somehow to protect the user-Document to allow only writes to the "graphs" field if the user is listed as owner in the graph.
Thats why I want to add another security-rule, something like this:
match /users/{userId} {
allow update: if request.auth.uid == userId && hasSingleChange() && get(/databases/$(database)/documents/graphs/getChangedKey()).data.owner).keys().hasAny($(userId))
}
function hasSingleChange(){
let changedKeys = request.resource.data.diff(resource.data).affectedKeys()
return changedKeys.size() == 0;
}
//To get the id of the changed graph
function getChangedKey() {
let changedKeys = request.resource.data.diff(resource.data).affectedKeys()
return changedKeys[0];
}
But as mentioned this is not working as I am unable to extract the changed graph-Id to lookup the document (getChangedKey not working).
Maybe there is another way arround to fullfill my requirements? What I try to avoid is to entitle the userId directly on the node, as I would have to update a lot of nodes if someone entitles a new user to a specific graph.
As you want an end-user to be able to modify only a document's field. You can check the section “Preventing some fields from being changed
” by referencing the documentation. You can try with the permission below.
allow update: if (!request.resource.data.diff(resource.data).affectedKeys()
.hasAny(['A', 'B']));
You check the discussion on stackoverflow link & Stackoverflow url.

Firebase database security rule, check auth.email in resource.data

Error [firebase.firestore] FirebaseError: Missing or insufficient permissions.
I have an object that a few users are allowed to update. An admin will set their email, and these users who sign in with those email will be allowed to update.
These are the rules that I have tried:
allow update: if request.resource.data.managerEmails.val().contains(request.auth.email) && request.resource.data.id == resource.data.id;
allow update: if request.resource.data.managerEmails.contains(request.auth.email) && request.resource.data.id == resource.data.id;
allow update: if request.resource.data.managerEmails.includes(request.auth.email) && request.resource.data.id == resource.data.id;
The resource to update:
{
id: "someid",
...fields,
managerEmails: "abcde#email.com,anothermanager#email.com",
}
User auth who is updating:
{
uid: "rSTLnYD9aisyZJHQPC6sg7mlsZh1",
email: "abcde#email.com",
...
}
Update:
Using request.auth.uid has been working in other rules, but in this case, I have to use emails because the users might not have signed up yet.
Using Rules Playground, I get Property email is undefined on object. Maybe using request.auth.email is not possible?
I was having the same problem but switched from using firebase.auth.email to firebase.auth.token.email and it now works.
I strongly suggest doing two things.
Firstly, store UIDs instead of email addresses. A UID is the preferred way to identify a single Firebase Auth account and is always guaranteed to exist and be unique. Email addresses are not.
Secondly, store the list of users as an array type field instead of comma separated string. This will be much easier to manage overall.
After you do these two things, the rule becomes simple:
allow update: if resource.data.managerUids.hasAny([request.auth.uid]);
use: request.auth.token.email insted request.auth.email
documentation here

Firestore rules: Prevent overwrite of property

In Firestor rules, how can I allow new documents to be created to a collection, and new values to be added to a document but not values in the document to be overwritten?
Please see the following for a clearer understanding:
Collections -------------- Documents -------------- Values
Users -------------------- DonutCoder -------------- isAdmin, birthDate
Now, if I want to add email to the values under the document Donut Coder how can I do this but prevent overwriting (eg. changing isAdmin to true so the user gets more priveleges)
What I have tried: allow read, create - but this allows new documents to be created but no properties in the document to be changed.
1) Don't add any code that overwrites the same path.
2) TO prevent others from reverse-engineering and update it, add this:
allow update: if false
Again anyone can still attempt to create the same document again so you need to add more security rules to it. Do check the official documentation for that.
I have answers regarding similar questions but they refer to realtime database. Though you can check it here.
The allow update: if false just prevent updating the existing value.
This official documentation page seems to show how to do this: https://firebase.google.com/docs/firestore/security/rules-fields#preventing_some_fields_from_being_changed
allow update: if (!request.resource.data.diff(resource.data).affectedKeys().hasAny(['average_score', 'rating_count']));
Preventing isAdmin or birthDate being updated:
allow update: if request.resource.data.isAdmin = resource.data.isAdmin
&& request.resource.data.birthDate = resource.data.birthDate;
You can start building on this depending on your needs. For instance, if you want to allow only logged in user and modifying only one field at a time:
allow update: if request.auth.uid != null
&& request.resource.data.diff(resource.data).affectedKeys().size() == 1
&& request.resource.data.isAdmin = resource.data.isAdmin
&& request.resource.data.birthDate = resource.data.birthDate;

Unable to base security rule condition on resource data in Firebase

I am attempting very simple thing and that is matching request.auth.uid to a field value in my transaction documents (like this resource.data.useruid) in Firebase security rule in order to get transactions of a particular logged in user. However, I don't get any documents while querying for them and get an error instead.
This is how the collection looks like - just one document there with useruid field.
The field's value is mapped to the users uid (screenshot taken in the Authentication -> Users tab.
And the rule looks like this
I should get the one document back but every time I query the documents with that user logged in (I am using angularfire2 for those purposes) I get Error: Missing or insufficient permissions.
If I modify the rule condition to return always true or if I only check for truthiness of request.auth.uid I get the query result alright. The funny thing though is that with resource.data involved - eg. checking for value of the amount field in the firebase rule - the condition is never met. I tried to write it like
allow read, write: if resource.data.amount == 3
and got the error again. Seems like I don't get the resource.data Map at all.
I feel like I am missing something obvious, although after reading the guides, it seems alright to me and I am already out of ideas. The debugging capabilities (or lack of) make the whole process very slow.
Could you please explain to me, why I don't get the resource.data Map in the firebase security rule or point me to a place where the problem might be?
You have most probably missed one specific point in the doc: your query fails "because it does not include the same constraints as your security rules". See https://firebase.google.com/docs/firestore/security/rules-query#secure_and_query_documents_based_on_authuid
The following, with your security rules works perfectly:
firebase.auth().signInWithEmailAndPassword("xxxx#xxxx.com", "xxxxx")
.then(function (info) {
db.collection("transactions").where("userid", "==", info.uid).get().then(function(querySnapshot) {
querySnapshot.forEach(function(doc) {
console.log(doc.id, " => ", doc.data());
});
});
});
If you remove the where clause, you get the exact error you are getting

Resources