how to avoid permission denied with a subcollection in firestore? - javascript

I have a denied permission when i tried to read my subcollection .
This is my collection and subcollection i have Teams/membersList
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
//Grants only a user access to its own data
match /users/{userId} {
allow read, write: if request.auth.uid == userId;
}
//Allow requests from authenticated users
match/Users/{document=**}{
allow read, write: if request.auth != null;
}
match/Teams/{document=**}{
allow read, write: if request.auth != null;
match /membersList/{membersList} {
allow read, write: if request.auth != null;
}
}
match /Teams/{userId} {
allow read, write: if request.auth.uid == userId;
}
match /Teams/memberLists/{document=**}{
allow read, write: if request.auth != null;
}
}
}
this is the part of my code with a denied permission
let fetch = async () => {
firestore()
.collection("Teams")
.where("uid", "==", await AsyncStorage.getItem("userID"))
.get()
.then((querySnapshot) => {
if (querySnapshot.empty) {
console.log("no documents found");
} else {
querySnapshot.forEach(async (doc) => {
let Teams = doc._data.Activity;
console.log(Teams);
updateActivity((arr) => [...arr, Teams]);
console.log(Activity);
firestore()
.collection("membersList")
.get()
.then((documentSnapshot) => {
console.log("User exists: ", documentSnapshot.exists);
if (documentSnapshot.exists) {
console.log("User data: ", documentSnapshot.data());
}
});
});
}
});
I've still have permission denied after writing this rules. Do you have any ideas ??

This code:
firestore()
.collection("membersList")
.get()
This reads from a top-level collection called membersList, which doesn't exist in your rules. So the user doesn't have access to the data, and the read gets rejected. Even if the collection doesn't exist, the read gets rejected.
If you want to read from the membersList subcollection of the current team, that's be:
doc
.ref
.collection("membersList")
.get()

Related

Denied permission by Firebase

I work with Firebase and for my Firestore Database I created the rules:
// Budgets
match /budgets/{budget} {
allow read: if request.auth != null;
allow update, delete, create: if request.auth != null && request.auth.uid == request.resource.data.userId;
}
Somehow Firebase allows me to create a budget. But unfortunately when I try to delete created budget I receive error:
FirebaseError: [code=permission-denied]: Missing or insufficient permissions.
Code for deleting a budget:
async function deleteBudget({ id }) {
try {
const budgetToDeleteQuery = query(collection(db, 'budgets'), where('id', '==', id), where('userId', '==', currentUser?.uid));
const budgetToDeleteData = await getDocs(budgetToDeleteQuery);
const budgetToDelete = doc(db, 'budgets', budgetToDeleteData.docs[0].id);
await deleteDoc(budgetToDelete);
} catch (error) {
alert(error);
}
// setBudgets(prevState => prevState.filter(item => item.id !== id));
}
Can anyone tell me what's the problem here?
To delete, use resource because it is the document before the operation takes place, which in this case is the budget you want to delete.
Check the code below.
// Budgets
match /budgets/{budget} {
allow read: if request.auth != null;
allow update, create: if request.auth != null && request.auth.uid == request.resource.data.userId;
allow delete: if request.auth.uid == resource.data.userId
}
Why you don't use
allow write:
instead of
allow update, delete, create:

Nuxt + Firebase FirebaseError: Missing or insufficient permissions

I am trying to read a collection of documents only if the document has the user id of the current user logged in with firebase. Here are my database rules:
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /todos/{todoId} {
allow read: if isLoggedIn() && request.auth.uid == resource.data.userId;
allow create: if isLoggedIn() && request.auth.uid == request.resource.data.userId;
allow update, delete: if isLoggedIn() && request.auth.uid == resource.data.userId;
}
function isLoggedIn() {
return request.auth != null;
}
}
}
Creating documents is not a problem, but reading them. Here is my function that retrieves the data:
getData () {
this.$fire.firestore.collection('todos').get()
.then((doc) => {
if (doc.exists) {
console.log('Document data:', doc.data())
} else {
// doc.data() will be undefined in this case
console.log('No such document!')
}
}).catch((error) => {
console.log('Error getting document:', error)
})
}
It seems that the resource.data.id rule is not pointing to the todo userId and therefore I get the FirebaseError: Missing or insufficient permissions. Here is my current database structure:
Any ideas on why the rule is not working as intended?
Firebase security rules don't filter data. Instead they merely ensure that any operation you perform meets the rules you've set.
So this rule you have says that the user can only read documents that have their own UID:
allow read: if isLoggedIn() && request.auth.uid == resource.data.userId;
But then your code goes ahead and tries to read all documents:
this.$fire.firestore.collection('todos').get()
Since your rules don't allow reading all documents, the operation is rejected.
To allow the operation, ensure that your read operation matches your rules and only request documents where the UID matches:
let uid = ...
this.$fire.firestore.collection('todos').where("userId", "==", uid).get()
I already solved the issue, the method for getting the data from firestore was incomplete, the solution was in the firebase documentation.
The correct way to do the data retrieving is using the where constraint:
getData () {
this.$fire.firestore.collection('todos').where('userId', '==', this.user.uid).get()
.then((docs) => {
if (docs) {
// console.log('Document data:', docs.data())
docs.forEach((doc) => {
console.log(doc.data())
})
} else {
// doc.data() will be undefined in this case
console.log('No such document!')
}
}).catch((error) => {
console.log('Error getting document:', error)
})
}

Adding docs to Firebase in Prod Mode using #angular/fire

Im currently having troubles adding documents to a collection. I allowed read, write, create, update, delete privileges on authenticated users but it i get following error message:
ERROR Error: Uncaught (in promise): FirebaseError: [code=permission-denied]: The caller does not have permission
FirebaseError: The caller does not have permission
Rules on Firebase(Firestore):
service cloud.firestore {
match /databases/{database}/documents {
match /{document=**} {
allow read, write, create, update, delete: if request.auth != null;
}
}
}
Code trying to add a doc to firestore:
createUserEntry(user) {
return new Promise((resolve, reject) => {
this.firestore.collection('users').add(
this.toFirebaseUser(user))
.then((success) => {
resolve(success);
})
.catch((error) => {
this.alert.alertToast(error.message);
reject(error);
});
});
}
toFirebaseUser(user) {
console.log(user);
return {
displayName: user.displayName,
email: user.email,
uid: user.uid
};
}
Would be great if anyone knows what's causing the issue.

How to fix firebase "User is not authorized" error even though user is authenticated in registration flow?

Im trying to do a variety of firebase actions in one call in a react-native app using react-native-firebase. the flow goes something like this:
create user in authentication
send image to storage
send data to firestore
During the image-storage phase, the imgRef.putFile() function errors out saying the user isn't authorized. However im using createUserWithEmailAndPassword() (which authenticates a user in on completion) and then using the returned credential to do the rest of the work, image storage and firestore creations.
firebase storage rules are set to allow only authenticated users. here's the ruleset:
rules_version = '2';
service firebase.storage {
match /b/{bucket}/o {
match /{allPaths=**} {
allow read, write: if request.auth != null;
}
}
}
Also, I have enabled anonymous signin in authentication methods.
here's the initial action up until the error point:
return (dispatch) => {
dispatch({ type: types.REGISTER_USER });
console.log('starting registration process...');
firebase
.firestore()
.collection('users')
.where('username', '==', username)
.get()
.then((querySnapshot) => {
console.log(querySnapshot);
if (querySnapshot.empty !== true) {
registrationFail(dispatch, 'Username already taken. Try again.');
console.log("Registrant's username already exists");
} else {
console.log('Registrants username is unique');
firebase
.auth()
.createUserWithEmailAndPassword(email, pass)
.then((userCredential) => {
uploadImg(dispatch, img, userCredential.user.uid)
here's the uploadImg() function:
const uploadImg = async (dispatch, uri, uid) => {
console.log('Starting image upload...');
dispatch({ type: types.UPLOAD_IMG, info: 'Uploading profile image...' });
const uploadUri = Platform.OS === 'ios' ? uri.replace('file://', '') : uri;
const imgRef = firebase.storage().ref('profile-images').child(uid);
return imgRef
.putFile(uploadUri, { contentType: 'image/png' })
.then((success) => {
console.log('profile image upload successful.')
return imgRef;
})
.catch((err) => {
console.log('profile image upload failed: ' + err)
uploadImgFail(dispatch, err.message);
});
};
again, the error that logs in the console is:
profile image upload failed: Error: User is not authorized to perform the desired action.
logging the firebase.auth().currentUser right before the uploading() function returns the current user object successfully.
this security rule issue also happens with Firestore, despite my security ruleset for the given collection being:
match /databases/{database}/documents {
match /users/{uid} {
// allow new user to check phone numbers
allow read: if true
allow update, create: if request.auth != null
allow delete: if request.auth.uid == uid
}
}
This is a part of my registration flow. I collect input, send relevant data to redux action, create a user, once the user is created, I add some data to a document in firestore. this makes no sense. im referencing the documentation and it still doesn't work.
How can this be fixed?
It seems to be an issue with firebase that you have to logout() the user once before you can log in after user creation. I faced the same issue and this is my workaround:
firebase.auth().signInWithEmailAndPassword(userEmail,userPass).catch((error) => {
/*console.log('Could not log in user');*/
console.log(error);
firebase.auth().createUserWithEmailAndPassword(userEmail,userPass).catch((error) => {
/*console.log('Could not create user');*/
console.log(error);
}).then(result => {
firebase.auth().signOut().then(() => {
firebase.auth().signInWithEmailAndPassword(userEmail,userPass).then(result => {
/*console.log("here is you logged in user");*/
})
});
});
});
});

Updating private field with Firebase Cloud: best practice for security

I'm using Firestore with some private documents (no write). My rules are already setup for this. For example, a document could contain the credits or subscription tier for a user. I want to let the backend update these fields instead of the client, for obvious reasons. However, I was wondering, if I create a generic updatePrivateField method in Cloud functions, would it be considered best practice?
exports.updateProtectedField = functions.https.onCall(async (data, context) => {
if (!context.auth) {
throw new functions.https.HttpsError(
"failed-precondition",
"Authentication Required"
);
}
const { collection, id, update } = data;
try {
await admin
.firestore()
.collection(collection)
.doc(id)
.update({
...update,
});
return { msg: "Update successful", code: 200 };
} catch (error) {
throw new functions.https.HttpsError("unknown", error.message, error);
}
});
Basically, what I am wondering is, is creating an endpoint like this considered safe? I am checking if the user is authenticated, but couldn't they just POST to the endpoint with their own login credentials and update any field in the database?
Thanks, I appreciate any help!
In case of a user can update own document.
Should set context.auth.uid as document id.
exports.updateProtectedField = functions.https.onCall(async (data, context) => {
// Check context.auth.uid
if (!context.auth || !context.auth.uid) {
throw new functions.https.HttpsError(
"failed-precondition",
"Authentication Required"
);
}
const { collection, update } = data;
// Set context.auth.uid as document id
try {
await admin
.firestore()
.collection(collection)
.doc(context.auth.uid)
.update({
...update,
});
return { msg: "Update successful", code: 200 };
} catch (error) {
throw new functions.https.HttpsError("unknown", error.message, error);
}
});
In case of a some role (ex. admin) can update a user document.
Should use Custom Claims and check it.
See https://firebase.google.com/docs/auth/admin/custom-claims
ex. Use a admin role
// Add any trigger or any condition you want.
// Set admin privilege on the user corresponding to uid.
admin.auth().setCustomUserClaims(uid, {admin: true}).then(() => {
// The new custom claims will propagate to the user's ID token the
// next time a new one is issued.
});
exports.updateProtectedField = functions.https.onCall(async (data, context) => {
// Check user is admin
if (!context.auth || !context.auth.token.admin) {
throw new functions.https.HttpsError(
"failed-precondition",
"Authentication Required"
);
}
const { collection, id, update } = data;
try {
await admin
.firestore()
.collection(collection)
.doc(id)
.update({
...update,
});
return { msg: "Update successful", code: 200 };
} catch (error) {
throw new functions.https.HttpsError("unknown", error.message, error);
}
More documents
https://firebase.google.com/docs/reference/functions/providers_https_.callablecontext#auth
https://firebase.google.com/docs/reference/admin/node/admin.auth.DecodedIdToken
https://cloud.google.com/functions/docs/concepts/functions-and-firebase
https://firebase.google.com/docs/auth/admin/verify-id-tokens#verify_id_tokens_using_the_firebase_admin_sdk
https://cloud.google.com/endpoints/docs/openapi/authenticating-users-firebase
Your Cloud Functions code allows any authenticated user to update any document. It is pretty much the equivalent of these security rules:
service cloud.firestore {
match /databases/{database}/documents {
match /{collection/{document=**} {
allow write: if request.auth != null;
}
}
}
If that is what you want to accomplish, I recommend doing so with the above security rules as it'll be both simpler and cheaper than introducing Cloud Functions to the mix.
If a user should only be able to update their own document through this (as zhoki's answer suggests by using context.auth.uid), then that'd be the equivalent of these security rules:
service cloud.firestore {
match /databases/{database}/documents {
match /{collection}/{userId} {
allow write: if request.auth != null && request.auth.uid == userId;
}
}
}
If this is the use-case you're looking for, I'd again recommend using security rules to secure it and bypass Cloud Functions for a simpler and cheaper solution.
In both of the cases above the {collection} allows the user to update documents in any collection, since that is what your Cloud Functions code also seems to do. It is much more common to limit the update to a specific collection, in which case you'd replace {collection} with just that collection name.

Categories

Resources