firestore security referring to data - javascript

i have a firestore db and it has some collection. each collection has some documents and the document will have a field called userId. This userId is the thing i want to match when i write the security rule. My data looks like this. look at the users collection one particular object.
Now i want the security rule something like this:
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /Users/{User}{
allow read, write: if resource.data.userId == request.auth.uid;
}
}
}
when i make a call to read the data:
import firestore from '#react-native-firebase/firestore';
console.log(`made db call to update menu for userid ${user.uid}`);
try{
var querySnapshot = await firestore().collection('Users').get();
console.log(querySnapshot.size);
querySnapshot.forEach(documentSnapshot => {
console.log('snapshot ID: ', documentSnapshot.id, documentSnapshot.data());
});
here is the error i get along with the logged userid used:
[Fri Dec 11 2020 15:53:22.649] LOG made db call to update menu for userid CKKMUujnojNiUGO7z8FHxtnquQ53
[Fri Dec 11 2020 15:53:22.808] LOG [Error: [firestore/permission-denied] The caller does not have permission to execute the specified operation.]
i get this error:[Error: [firestore/permission-denied] The caller does not have permission to execute the specified operation.] i am basically not able to put any condition that relies on resource.data. i hardcoded id once and put request.auth.uid =="hard coded id" and that also worked. i think somehow resource.data.userId is not resoving although it looks correct to me.

It's important to realize that Firestore security rules are not filters. Be sure to read that linked documentation. Security rules will not filter the documents to match only what a user is allowed to read.
Your query is demanding all of the documents in Users:
firestore().collection('Users').get();
However, your rules insist that a user must only request documents where their UID is in the userId field of the document:
allow read, write: if resource.data.userId == request.auth.uid;
You can make your query match the requirements of the rules by adding a filter on the userId field:
firestore()
.collection('Users')
.where('userId', '==', user.uid)
.get();

Related

Firebase insufficient permissions - what am I doing wrong?

Sorry I am new to Firebase, I am trying to deploy my app to production for the first time but I am struggling with the security rules.
I have a page in my next.js project which pulls data from the firestore (a nested subcollection), like so:
useEffect(() => {
const getKids = async (user: any) => {
if (user) {
const collectionRef = collectionGroup(db, 'kids')
console.log(collectionRef, 'collectionRef')
const q = await query(collectionRef,
where("uid", "==", user.uid)
)
console.log(q, 'q')
const data = await getDocs(q)
data.forEach(doc => {
setKids(data.docs.map((doc) => ({
...doc.data(), id: doc.id
})))
})
}
}
getKids(user)
}, [user?.uid])
It also writes to it on submit, but I'm just trying to read the data onto the page first (with no luck so far)...
Here's how my firestore looks:
My rules:
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /users/{uid}/kids/{document=**} {
allow read, write: if request.auth.uid == uid;
}
}
}
With this I would expect the currently logged in user to be able to view all the documents inside of the kids subcollection inside of users, but it doesn't work.
What am I doing wrong?
Your rule only protects documents nested under users. But your query is using a collection group which could have instances living anywhere. If you review the documentation on security rules for collection groups you'll see that you need to use a different form to allow access for collection group queries.
match /{path=**}/kids/{post} { ... }
However, now you don't have a UID in the path to use to protect the collection group, because collection groups can live anywhere.
The bottom line here is that you'll have to do one of two things:
Don't use a collection group query, and instead refer to the specific subcollection using its full path under users for a specific uid.
Relax your rules somehow for the collection group query to work.

Deleting and updating docs for firestore security rules in local emulator

Using the local emulator, I'm trying to test a rule for my delete & update which looks like this:
match /posts/{postId} {
allow read: if true;
allow create: if true;
allow delete, update: if (resource.data.user.uid == request.auth.uid)
}
In my test file, I write the path of the exact location of the existing doc that I'm trying to perform for example a delete on:
it("If creator, Allow delete/update items in the post/comment collection", async () => {
const db = firebase
.initializeTestApp({ projectId: MY_PROJECT_ID, auth: myAuth })
.firestore();
const testDoc = db
.collection("general")
.doc("spaces")
.collection("spaces")
.doc("9LbUetZxWeL1ln5hv9ug")
.collection("posts")
.doc("RtUnezdAgXUfWmRSPpkF");
await firebase.assertSucceeds(testDoc.delete());
});
However when I run this test, it gives me this error. However, I know this doc exists and so does the user.uid I am looking to grab in my resource. Is there something wrong in how I'm trying to use this emulator?
I also saw that if I go into my local emulator firestore database, I can hard-code a file, specifically call route my test to it, then it works.
It looks like no user is signed in when you run the test.
Since your security rules check request.auth.uid, they reject the operation when request.auth is null. Check myAuth.currentUser.uid before running the test to ensure it has a value.

How to access all data in a firestore database with admin approval?

I am currently building a vuejs web app that uses firestore from firebase. The basic structure is a user fills out an application with data and then an admin can look at that data to see if they are eligible for funding. The problem that I am encountering is that when the admin requests the information from firestore, it only ever returns an empty array.
The admin tag is assigned using this firebase cloud function:
const admin = require('firebase-admin');
admin.initializeApp();
exports.addAdminRole = functions.https.onCall((data, context) => {
//get user and add custom claim (admin)
return admin.auth().getUserByEmail(data.email).then(user => {
return admin.auth().setCustomUserClaims(user.uid, {
admin: true
});
}).then(() => {
return {
message: `Success! ${data.email} has been made an admin`
}
}).catch(err => {
return err;
});
});
My firestore structure is as follows:
users
|_user1_uid
|_info
|_user2_uid
|_info
The call I am making to get the information is this:
db.collection('users')
.get()
.then(snapshot => {
console.log(snapshot.docs)
})
As you can see, the call is supposed to get a snapshot of all of the documents I have. (example: the console.log should show that I have three users > (3) [n, n, n]). However, what is returned instead is an empty array.
My firestore rules are this:
service cloud.firestore {
match /databases/{database}/documents {
match /users/{userId} {
allow read, write: if request.auth.token.admin == true;
}
match /users/{userId}/{document=**} {
allow create: if request.auth != null;
allow read, write: if request.auth.token.admin == true;
allow read, write: if request.auth.uid == userId;
}
}
}
Any suggestions or pointers on why my admin is unable to get the users' information would be appreciated.
Thanks!
Ok, I figured out what was happening. When I start making my documents I was using this code:
db.collection('users').doc(this.uid)
.collection('info').doc('county').set({
county: this.county
)}
What I didn't realize is that this makes the document that holds this.uid to be virtual. If I had looked a little harder I would have seen this notification on firebase:
I found a relatively simple workaround which was first creating the UID document with dummy data before adding actual info to it like so:
db.collection('users').doc(this.uid).set({
dummy: 'dummy'
})
This fixed my problem. Thanks everyone for your help! I appreciate it!

how to set permissions in firebase firestore

$path = "firebase_auth.json";
$config = array(
"projectId" => "XXXX",
"keyFile" => json_decode(file_get_contents($path), true)
);
$firestore = new FirestoreClient($config);
$collectionReference = $firestore->collection('Channels');
$snapshot = $collectionReference->documents().get();
Response of this code is
An uncaught Exception was encountered
Type: Google\Cloud\Core\Exception\ServiceException
Message: { "message": "Missing or insufficient permissions.", "code": 7, "status": "PERMISSION_DENIED", "details": [] }
Filename: /var/www/html/riglynx/vendor/google/cloud/Core/src/GrpcRequestWrapper.php
Line Number: 263
check out Get started with Cloud Firestore Security Rules documentation. And see Writing conditions for Cloud Firestore Security Rules documentation.
One of the most common security rule patterns is controlling access
based on the user's authentication state. For example, your app may
want to allow only signed-in users to write data:
service cloud.firestore {
match /databases/{database}/documents {
// Allow the user to access documents in the "cities" collection
// only if they are authenticated.
match /cities/{city} {
allow read, write: if request.auth.uid != null;
}
}
}
This should help you get started.
Reason you are getting the error is because you are not allowed to access documents of the collection called Channels.
In order to fix this, you have login to your console firebase.
Navigate to Database > Under firestore database, you have to click on Rules.
Under rules, you can give permission as per your wish.
If you want to give access to al the users then you can simple replace current code with the following code. (Not a good practice and not secure too)
service cloud.firestore {
match /databases/{database}/documents {
match /{document=**} {
allow read, write;
}
}
}

Cloud Firestore with Authentication

I have been looking for a way to use Authentication with Firebase's new Cloud Firestore. I know it's possible, but there is no good guide both in the Documentation and other places.
Could someone please explain how to provide an Authentication UID when get()ing data from Cloud Firestore?
Here are my security rules:
service cloud.firestore {
match /users/{userID} {
allow read, write: if request.auth.uid == userID;
}
}
And here is my current code:
var db = firebase.firestore();
var docRef = db.collection("users").doc(uid); //lets say "uid" var is already defined
docRef.get().then(function(doc) {
if (doc.exists) {
console.log("Document data:", doc.data());
} else {
console.log("No such document!");
}
}).catch(function(error) {
console.log("Error getting document:", error);
});
The structure of my database is just a "users" collection at root, then a document for each user (named after the UID). I want to get the document with the user's UID.
Of course, this gives the error "Missing or insufficient permissions," which is expected, because of the security rules.
This question may seem simple, but if anyone can find some good documentation on this, that would be great!
I was just confused about how UIDs work. I was under the impression that you have to supply a UID and an auth token to database operation.
You don't actually pass in a UID to database operations. The firebase object stores authentication state. You can simply sign in (check the Firebase docs to read how to do this), and after the action is completed (use a .then promise statement) you can simply do your operation.
Additionally, to have users not sign in every time they visit your app, you can have the `firebase object's authentication state persistent.
Your rule should be inside match /databases/{database}/documents
service cloud.firestore {
match /databases/{database}/documents {
match /users/{userID} {
allow read, write: if request.auth.uid == userID;
}
}
}

Categories

Resources