I've found that question relatively often asked here, but i still cant figure out how to manage a rule for unique properties. I have following document datamodel:
users/{usereId}/Object
users/usernames/Object
The first Object contains basic information about the user, like:
{
email: "example#hotmail.edu"
photoURL: null
providerId: null
role: "admin"
username:"hello_world"
}
meanwhile the usernames objects only contains the username as the property key and the uid as the value, for instance:
{
hello_world:"F3YAm8ynF1fXaurezxaQTg8RzMB3"
}
I set it up this way, because I want that every user has a unique username. And its less time consuming iterating through the second object than through the first ones.
But back to my issue. I need that hello_world is unique within the write operation. But my rules so far does not work. I have:
service cloud.firestore {
match /databases/{database}/documents {
match /{document=**} {
allow read, write: if request.auth.uid != null
}
match /users/{userID} {
allow create: if !exists(/databases/$(database)/documents/users/$(request.resource.data.username)) <== does not apply
}
}
}
The second match is, what should apply the unique property rule. Has anyone an idea how to set the rule correctly?
In the console the object model looks as follows
You created a document called usernames in your users collection, to track the names that are in use. But your rules are trying to find a document named after the current user's name, which will never exist in this structure.
In your current structure, you will need something like this:
allow create: if get(/databases/$(database)/documents/users/usernames).data[$(request.resource.data.username)] == request.auth.uid
So the above gets the document where you keep all the user names, and then checks if the current user name is in their for the current UID.
An alternative approach is to keep an additional colllection of all user names, and then make each document in there map a single user names to a UID. In this scenario your /usernames collection would be top-level, since it's shared between all users. The rules for this would be closer to what you currently have:
allow create: if !exists(/databases/$(database)/documents/usernames/$(request.resource.data.username))
Since your usersnames have to be unique, wouldn't it be an option to use their names as the document key?
Related
My Question
Is it possible to create firebase rules such that the user can read certain data fields in a document but not read other rules in the same document?
I'm asking this question because I want to enable users to query data from documents but only certain fields in the document.
For example
Assume that you have the following data in a story-document:
/stories/{storyid}
{
title: "A Great Story",
content: "Once upon a time...",
author: "some_auth_id",
published: false
}
In the example below I have attempted to create a rule such that users can read/query the author of a document (but not other fields of a document - for example title).
Notice that I have added /stories/{storyid}/{author}
service cloud.firestore {
match /databases/{database}/documents {
match /stories/{storyid}/{author} {
allow read: if request.auth != null;
}
}
}
How would I go about to create such a rule?
I guess that you mean "Is it possible to create firebase rules such that the user can read certain data fields in a document but not read other fields in the same document?"
This is actually not possible: Firestore Security Rules apply at the level of the document.
One classical solution is to duplicate the documents in another collection with only the subset of the fields you want to make readable.
I am developing a Flutter app and using Firebase as backend. In my app, each user needs o sign up to be able to use the app and the user's profile data is saved in a user_profile collection.
Each user's profile data is stored in a separate document in the collection.
The Document ID for each document is equal to the User ID, created by the Firebase Authentication when the user signs up for the first time.
And I also save the User ID in a field (named uid) in each document as well for the corresponding user.
For the security part, I want that each user may only read his/her own profile data. I set the following rule:
// Rules for User Profile data
match /user_profile/{any} {
allow read: if (request.auth != null) &&
(resource.data.uid == request.auth.uid) &&
exists(/databases/$(database)/documents/users/$(request.auth.uid));
Is it correct if I set my rules as given in the above example?
(1) The user needs to be authenticated
(2) The uid in the incoming request needs to be equal to the uid field in the corresponding document that the user wants to read.
(3) The document with the uid available in the request must exist in the corresponding document
I cannot make it clear to me if I am making the whole thing unnecessarily complicated. For instance, does the rule (1) do the same thing as rule (2)? While I have rules (1) and (2), does it add anything to have rule (3) as well?
Based on the following elements in your question:
The user's profile data is saved in a user_profile collection
The document ID for each (user's profile) document is equal to the User ID
You want that each user may only read his/her own profile data
the following read security rule should do the trick:
service cloud.firestore {
match /databases/{database}/documents {
// Match any document in the 'user_profile' collection
match /user_profile/{userId} {
allow read: if request.auth != null && request.auth.uid == userId;
// ...
}
}
}
In other words, since the document ID for each (user's profile) document is equal to the userId you don't need to use the field containing the userId in the security rule: The wildcard expression {userId} makes the userId variable available in rules, see the doc.
I am trying to figure out how should I write my firestore rule such that Only the matching uid in the key of the document's key-value pair can have permission to write.
More specifically:
my firestore structure is something like:
'Items LA' -> 'Pasadena' -> {'awjij53dHh3dnYAh': {itemInformation}}
collection -> document -> fields
where 'awjij53dHh3dnYAh' is the seller ID.
and I want to write a rule such that it gives awjij53dHh3dnYAh permission to write in that field.
Currently what I have is the following:
match /Items%20LA/{cityName}/{data}{
allow read: if request.auth != null;
allow write: if request.auth.uid == data.key; // This is where I am not sure how to write what I want to accomplish
}
Thanks
First thing - the match path for a rule only includes the path components of a document (collections and document IDs), and not fields. Also, it doesn't need any URL escaping. Just match on "/Items LA/{cityName}".
Second, if you want to refer to the names of fields in a document in a rule expression, you should know that request.resource.data is a Map of all the incoming fields, and you will have to use its API to deal with the those key/value pairs.
Third, if you want to find out which fields of a document changed, you will need to compare the state of the document before and after the change using the MapDiff API.
Putting all this together, you will need to check that only the field changed that equals the user's UID:
match /Items LA/{cityName} {
allow write: if request.resource.data.diff(resource.data).affectedKeys().hasOnly([request.auth.uid]);
}
What you want probably is
allow write: if request.auth.uid == $(data)
//or this
allow write: if request.auth.uid == resource.id
I have following character collection structure in my database (firestore)
/characters/{uid}
- username: string
- clan: string
- mana: number
- health: number
etc...
I am trying to figure out a security rule for /characters/{uid} with following logic
service cloud.firestore {
match /databases/{database}/documents {
// Characters
match /characters/{characterID} {
allow create: if isValidUsername();
}
}
}
here function isValidUsername checks for various things like length, special characters etc... but one thing I can't figure out is how to check following inside of the function
Make sure that request.resource.data.username is unique i.e. not present inside any other document of /characters collection.
TL;DR: Enforcing uniqueness is only possible by creating an extra collection.
In your current structure, to know if a username is unique, you will need to read each document. This is incredibly inefficient, and on top of that it isn't possible in security rules, since they can only read a few documents per rule.
The trick is to create an extra collection usernames, where you also have a document for each user, but now the key/ID of each document is the username. With such a collection, you can check for the existence of a certain document, which is a primitive operation in the security rules.
Also see:
Prevent duplicate entries in Firestore rules not working
I'm trying to set a rule so that only authenticated users can update, create, write and delete their cat documents. My problem is that I can not create a new document.
When I try to add it I get the error message "Error: Missing or insufficient permissions."
The strange part is that I can delete existing documents with this rule.
service cloud.firestore {
match /databases/{database}/documents {
match /cats/{catId} {
allow read;
allow update, create, write, delete: if resource.data.ownerId == request.auth.uid;
}
}
}
Maybe you are missing request e.g. request.resource.data.ownerId == resource.auth.uid
When writing data, you may want to compare incoming data to existing data. In this case, if your ruleset allows the pending write, the request.resource variable contains the future state of the document. For update operations that only modify a subset of the document fields, the request.resource variable will contain the pending document state after the operation. You can check the field values in request.resource to prevent unwanted or inconsistent data updates
https://firebase.google.com/docs/firestore/security/rules-conditions#data_validation