How to make certain data fields readable in a Firestore document - firebase

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.

Related

Check firestore field of parent document in firestore rules

I am seeking for your help!
I have a collection /releases which documents have always two sub collections releases/covers and releases/tracks.
I need firestore rules, which make all of these collections and subcollections readable from all logged in users.
Write and delete should only be possible by the owner of the document. On the parent document in the releases collection I have a field uid which matches the firebase auth uid. Also the uid should not be able to be changed ever. Its written on create of the document and from there the uid should at least never change its value.
What could be a firestore rule for this?
I have already made it for the releases document itself but a soon as it comes to the sub collections of it, there are problems with the access appearing.
If you want to allow read for every document in root collection and also all subcollections, you can use the following:
match /yourCollection/{restOfPath=**} {
allow read;
}
If you want to be specific:
match /yourColleciton/{docID}/subCollection/{subId} {
allow read;
}

Firebase security rules based on custom user field

I'm trying to set up security rules (use Firebase Cloud Firestore).
I changed the "users" table (added company_id field) and create "appointments" table (with company_id). I want to implement the following functionality (when a user requests appointments, he only receives appointments with his company id)
Wrote a rule:
match /appointments/{appointment} {
allow write;
allow read, update, delete: if resource.data.company_id == get(/databases/$(database)/documents/users/$(request.auth.uid)).data.company_id;
}
But my code throw error about permissions
const q = query(collection(db, 'appointments'), where("company_id", "==", company_id), orderBy("createdAt"));
Firestore doesn't magically know what documents have what company_id, so it tries to read all documents to return the documents that you want, but it can't, your security rules stop it.
Either get rid of the security rules, or find a way to structure your database so that you don't have this problem.
I don't know enough about your database to tell you a good structure, but what about something like this?
A collection called companies.
Each document in the collection is a company_id
A subcollection called users, where you put all users that belong to this company.
Another subcollection, called appointments, where you put all appointments for the company.
That way, you could write a security rule like so:
match /companies/{company}/appointments/{appointment} {
allow create: if true;
allow read, update, delete: if exists(/databases/$(database)/documents/companies/$(company)/users/$(request.auth.uid))
}
The problem with this approach is that, because we have divided all users and appointments into different collections, it will be impossible to query for all existing users or for all existing appointments.
It all depends on your use case.

Firestore - proper NoSQL structure for user-specific data

I'm working on an app with Firestore. I use Firebase Authentication for managing users.
The thing is that most of the data in my app would be user-specific. Meaning that users should only have access to their own data.
I've already done research about Firestore security rules, so I know how to handle the security part.
However, I'm wondering how to properly model user-specific data in general. First piece of data I'm adding are categories, that have (for now) 2 properties: name and type.
After some research, I found it's good to create a collection called categories and then a document per-user, each named after the user's ID (UID).
But then, within such a user-specific document, I want to add my categories. One way I figured it out is like on the screenshot below:
So, to describe this approach in a more generic way:
New collection for each data type (e.g. categories)
Inside the collection, separate document named with UID for each user
In each user-specific document, a map with an object's data (e.g. category_1 map with fields name = groceries and type = expense
However, what worries me here is that I need to somehow invent these names of the maps like category_1, category_2 etc... I have a feeling something is wrong in this model, but my strong SQL background doesn't allow me to think that through 😉
Do you have any ideas whether this model is a good one or what problems could it produce later? Maybe you can suggest a better approach for modeling user-specific data in Firestore database?
Is there any limit on how many categories can a single user have? If not then it'll be better to create a collection for categories to avoid hitting 1 MB max document size. If there is a limit and you decide to use a map, I'd recommend creating a map field categories and them as it's children as shown below so if you add any other fields in the document, it'll be much more categorized:
{
categories: {
category_1: {
name: "",
type: ""
},
category_2: {
name: "",
type: ""
}
}
}
However, creating a sub-collection could be better choice as well if each category gets more data in future and you need some queries on categories.
users -> {userId} -> categories -> {categoryId}
(col) (doc) (col) (doc)
As the categories are in a sub-collection you don't need to add userId field in every category document. However you can also create 2 different root collections namely - users and categories:
users -> {userId}
(col) (doc)
categories -> {categoryId}
(col) (doc)
In this case you would have to store userId field to filter between owner of those categories.
#Alex Mamo has perfectly explained difference between using a root level collection and a sub-collection in this post if you need an in-depth explanation: What are the benefits of using a root collection in Firestore vs. a subcollection?
Security rules will be different in both the cases. If you use sub-collections then you can just read user UID from the wildcard like this:
match /users/{userId}/categories/{categoryId} {
allow read, write: if request.auth != null && request.auth.uid == userId;
}
However, if you use a root level collection, the document ID for each category usually won't have user's UID but the UID will be stored in the document as a field. In that case you would have to read the userId from the document being requested.
match /categories/{categoryId} {
allow read, write: if request.auth != null && request.auth.uid == resource.data.userId;
}
what worries me here is that I need to somehow invent these names of the maps
NoSQL database don't have a schema. You can dynamically add any key as required. Just treat every document as a JSON object.
const newKey = "category_123"
const newValue = {name: "newCatName", type: "expense"}
const userDoc = firebase.firestore().collection("users").doc("userId")
userDoc.update({
[`categories.${newKey}`]: newValue
})
This update operation will create a new child in 'categories' map with the provided values.

Firestore security rule to check if character username already exists

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

firebase rule for unique property in firestore

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?

Resources