Google Firestore rules for a class blog - javascript

I volunteered to make a class blog for my English class where everyone can make an account and post stuff. I used Google firebase for this, the Authentication and Firestore. Anyone can sign up and post their thing. Everything works fine except for the database security rules, right now, it's on test mode so anyone can do anything to the data.
The structure of it is:
/users/{Authentication UID}:
- joined
- name
- uid (this is a token generated from javascript used for the profile page and others)
/posts/{PID}:
- date
- pid
- post
- title
- uid
/comments/{PID}:
- comment
- date
- pid
- uid
To clear up some things: there are two uids, one as the document name which is from the Authentication and another which is a token generated from JavaScript used for the posts, comments and profile page.
All I ask for is one simple thing. What rules should I use to secure the data? I had a look at the firebase docs but ended up with no luck.
EDIT 1: Changed the accepted answer's rules a bit and got it working (I think):
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /{document=**} {
allow read: if true;
}
match /users/{userId} {
allow write: if request.auth.uid == userId;
}
match /posts/{postId} {
allow create: if request.auth.uid != null;
allow update, delete: if get(/databases/$(database)/documents/users/$(request.auth.uid)).data.uid == resource.data.uid;
}
match /comments/{commentId} {
allow create: if request.auth.uid != null;
allow update, delete: if get(/databases/$(database)/documents/users/$(request.auth.uid)).data.uid == resource.data.uid;
}
}
}

Check out the following rules you can use for your firestore database
service cloud.firestore {
match /databases/{database}/documents {
//create document id with users UID assigned by firestore, while creating a users document, this rules allows users to read and write only their data
match /users/{userId} {
allow read, write: if request.auth.uid == userId;
}
//post id is random id generated by firebase
match /posts/{postId} {
//allows authenticated users to create or read posts
allow read, create: if request.auth.uid != null;
//allows users to update or delete only their posts.
allow update, delete: if get(/databases/$(database)/documents/posts/$(request.auth.uid)).data.uid == request.auth.uid;
}
//comment id is random id generated by firebase
match /comments/{commentId} {
//allows authenticated users to create or read comments
allow read, create: if request.auth.uid != null;
//allows users to update or delete only their comments.
allow update, delete: if get(/databases/$(database)/documents/posts/$(request.auth.uid)).data.uid == request.auth.uid;
}
}
}

Related

Cloud Firestore custom claims with query

I have a firebase collection notes where each document has a user-id array that contains some user ids.
I've set a custom claim on my user access token that is in the format of nid=true (where nid is the note document id they should have access to) however when I try and query the collection for any relevant documents I receive a permission error.
My query is written as follows:
const notesRef = collection(db, "notes");
const allNotesQuery = query(
notesRef,
where("user_ids", "array-contains", user.uid)
);
const noteDocs = await getDocs(allNotesQuery);
my security rules are:
match /notes/{nid} {
allow read, write: if request.auth != null && request.auth.token[(nid)] == true
}
I've also tried nid without the brackets around it like so, but it still doesn't work
match /notes/{nid} {
allow read, write: if request.auth != null && request.auth.token[nid] == true
}
Can anyone see anything obviously wrong with this? If I inspect my token I see the custom claim set correctly with the correct nid value. Unfortunately I can't find a way to test custom claims in the developer console.
*** edit ***
As a follow up to my comments on the first posted answer. The following doesn't work.
allow read, write, list: if request.auth != null && request.auth.token[nid] in resource.data.user_ids
but this does:
allow read, write, list: if request.auth != null && request.auth.uid in resource.data.user_ids
Firestore security rules don't filter the data. Instead they merely ensure that your app is not requesting more data than it's permitted to. For more on this, see the Firebase documentation on rules are not filters.
Since your code is not in any way filtering on the nid token (as far as I can tell), the rules reject the operation. So modify the query to only request only note IDs that the user has access to, and then the rule you wrote can validate it.

Make a rule that allows only those who are online from the database

I'm new to firebase and rules in firebase.
I want to do something like this:
I have firestore users, and each user has a boolean field isOnline, for example in the image I added:
I have a collection of users, inside I have the names of the users, and inside I have a field of isOnline.
Here in the picture, frontend4 user isOnline's field is true, so for him I will agree to use the site.
This is the rules I'm trying to do, I want to access my user's isOnline, and check if it's isOnline, and if so, then give it access.
The rules I made do not work, I do not quite understand why.
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /{document=**} {
allow read, write: if false;
}
match /users/{itemId}{
allow read, write: if itemId.isOnline = true;
}
}
}
You can use Data Validation as mentioned in the documentation here
service cloud.firestore {
match /databases/{database}/documents {
match /{document=**} {
allow read, write: if false;
}
// Allow the user to read data if the document has the 'isOnline' to true
match /users/{itemId} {
allow read: if resource.data.isOnline == true;
}
}
}

Firebase reading and writing data for users in one collection

I recently started a firebase project, and I need some help with the security rules. When A user signs up, I create a new document in a collection called "Users". The document name is encrypted by a server, and can only be decrypted by that same server. The problem I am having now is that if a malicious entity wanted to, they could get all of the documents in the collection by changing client-side code, and that would defeat the whole purpose of encrypting the data. So my question is: Is there a way to enforce that somebody can only read the data of their document in the collection, and block attempts to read the whole collection? (I am using Firestore by the way.)
Thanks so much!
FireBase Rules:
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /{document=**} {
match /users/{userId} {
//signed in users can get individual documents
allow get: if request.auth.uid != null;
//no one can query the collection
allow list: if false;
allow read, write;
}
}
}
}
You're duplicating rules:
//signed in users can get individual documents
allow get: if request.auth.uid != null;
//no one can query the collection
allow list: if false;
allow read, write;
Since allow read is a combination of allow list and allow get, that last line make the two lines above it useless.
The minimum change is to remove read from the allows:
//signed in users can get individual documents
allow get: if request.auth.uid != null;
//no one can query the collection
allow list: if false;
allow write;
I suspect you'll want to tighten the allow write to only allow users to write their own document, but that's a separate problem.

Setting Firebase Firestore security rules so only users can CRUD their own data and all else is ignored

I have an app that is designed so authenticated users via Google only have access to their own data with no "social" features. I want to know the security rules for the below criteria.
Let's say I have 5 collections and one of them is called "todos" and the data mirrors the other collections in that it has a field for the authenticated users uid. The typical document looks something like this:
Todos
todo:{
title:"some titled",
body:"we are the world , we are the children",
uid:"2378y4c2378rdt2387btyc23r7y"
}
Some other collection
thing:{
name:"some name",
content:"Some content",
whatever:"whu-eva",
uid:"2378y4c2378rdt2387btyc23r7y"
}
I want the authenticated Google user to be able to CRUD any data that has said users uid in the uid field. I want all other data to be inaccessible to the logged in user.
I want to know how to create rules for this scenario.
I'm mulling through the documentation now but I figure I might be able to save some time by asking.
I do not have specific roles for the app.
https://firebase.google.com/docs/firestore/solutions/role-based-access
As a side note, is their a feature in Firebase to automatically bind an authenticated Google users uid to documents created while they are logged in? (I am assuming the answer is no and I was planning on manually grabbing the uid in my app and setting it on the client prior to document creation).
Thank you.
Update
I tried using the code that Klugjo posted below.
When I try to test it in the simulator I get an error.
Here is my collection and a screenshot of the error.
Here is something else I tried:
Based on everything I've read it seems like the following code should work - but it doesn't. I've supplemented the key "userId" in place of " uid" that is written in the object data at the top of this post. I changed the key to distinguish it from the uid.
service cloud.firestore {
match /databases/{database}/documents {
match /todos/{id} {
allow read: if request.auth.uid == request.resource.data.userId;
allow create, update, delete:
if request.resource.data.userId == request.auth.uid;
}
}
}
I've created a video where I try to GET and CREATE a document.
I don't think I am using the testing feature correctly.
Video
https://www.youtube.com/watch?v=W7GZNxmBCBo&feature=youtu.be
EDIT
I have it working when I test with a hard-coded request.auth.uid.
In the image below I hardcoded "test" as the request.auth.uid.
My problem now is that I would really like to know how to test it in the rules editor without hard-coding this information.
Edit
Here is a video demo of the problem using a real app.
https://www.youtube.com/watch?v=J8qctcpKd4Y&feature=youtu.be
Here is a sample secure rule set for your requirements.
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /users/{id}/{u=**} {
allow read, write: if (isSignedIn() && isUser(id));
}
match /todos/{id}/{t=**} {
allow read, write: if (isSignedIn() && isUserOwner());
}
match /{document=**} {
allow read, write: if false;
}
function isSignedIn() {
return request.auth != null;
}
function isUser(uid) {
return uid == request.auth.uid;
}
function isUserOwner() {
return getResourceData().uid == request.auth.uid;
}
function getResourceData() {
return resource == null ? request.resource.data : resource.data
}
}
}
All documents are publicly inaccessible.
The rests will be decided based on the data already saved in DB and / or the data being sent by the user. The key point is resource only exists when reading from DB and request.resource only exists when writing to DB (reading from the user).
Documents under todos can be read and written only if they have a saved uid which is the same as the sent request's uid.
Documents under users can be read and written only if their document id is the same as the sent request's uid.
isSignedIn() function checks if request is authorised.
isUser(id) function checks if id matches the authorised request's uid.
isUserOwner() function checks if document's uid matches the authorised request's uid.
I think what you are looking for is the "resource" parameter in the security rules: https://firebase.google.com/docs/firestore/security/rules-conditions#data_validation
Try something like:
service cloud.firestore {
match /databases/{database}/documents {
match /todos/{id} {
allow read, write: if request.auth.uid == resource.data.userId;
}
}
}
EDIT:
Subcollection strategy
If you change your DB to look like the following:
/users/{userId}/todos/**
then you could allow users to read/write anything under their own document with the following rule:
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /users/{uid}/{doc=**} {
allow read, write: if request.auth != null && request.auth.uid == uid;
}
}
}
This would have the advantage of not needing to introspect the contents of the data which I believe might count against your read quota.
You are looking for something like this
service.cloud.firestore {
match /databases/{database}/documents {
match /todos/{userId} {
allow read, update, delete: if request.auth.uid == userId;
allow create: if request.auth.uid != null;
}
}
}
match /todos/{userId} makes the userId variable available in the rule condition
request.auth.uid matches the auth'd user uid

Preventing user sign-up in Firebase?

I've been working on a small application, using firestore. I also implemented basic auth using email/password (from the Firebase UI kit).
Now existing users (which I made manually) can login, but if the e-mail is not found, the auth let's you sign-up. Can this be disabled? Because I want to somehow restrict the access a atleast a little bit.
__
What I've done for now is wrote a db-rule so that only a user in my 'users'-collection (where document uid = userid) and has a boolean field 'admin' and give them write access.
The rule itself goes as follows:
service cloud.firestore {
match /databases/{database}/documents {
match /{document=**} {
allow read: if request.auth.uid != null;
allow write: if get(/databases/$(database)/documents/users/$(request.auth.uid)).data.admin == true;
}
}
}
is this 'safe' and 'ok' to be implemented like this?
As you can see on this community question, in firebase you cannot disable sign-up without disabling sign-in for all users, so in order to create this control you would have to either:
Set that on your provider, since you are using FirebaseUI, when you build the class doing something like this:
List<AuthUI.IdpConfig> providers = new ArrayList<>();
providers.add(new AuthUI.IdpConfig.EmailBuilder()
.setAllowNewAccounts(false)
.build());
Control it using Cloud Functions by doing something like this:
const admin = require('firebase-admin');
exports.blockSignup = functions.auth.user().onCreate(event => {
return admin.auth()
.updateUser(event.uid, {disabled: true})
.then(blockedUser => console.log(`The user ${blockedUser.toJSON()} has been blocked from SignIn`))
.catch(error => console.log(`${error}`));
});
The solution you already implemented that has a list of authorized users and that blocks out all users that are not, this is a good choice if you have a limited number of users. Also to you security point, this would only be visible to the firebase rules themselves and the users would still need to sign in so the rules can get the uids to compare with the list, so I would say that this would be secure enough.
Hope this helps.

Categories

Resources