problem:
when i use the firestore request emulator, it works how i want but when i test it through my app, it lets me make create requests even though i am not authenticated.
note i am using react, easy peasy state managment & firebase spark plan.
code:
rules
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /{document=**} {
allow write: if request.auth != null;
allow read;
}
}
}
submitting to database:
componentDidMount() {
try {
this._db = firebase.firestore();
this._db
.collection("txts")
.limit(50)
.orderBy("timeMade", "asc")
.onSnapshot((querySnapshot) => {
this.setState({ messages: [] });
querySnapshot.forEach((doc) => {
this.setState({
messages: [...this.state.messages, doc.data().txt],
});
});
});
} catch (err) {
alert("Must sign in first!");
}
}
uploadData = (e) => {
e.preventDefault();
if (this.state.m.trim()) {
this._db.collection("txts").add({
txt: this.state.m,
timeMade: Date.now(),
});
this.setState({ m: "" });
}
};
things i have tried:
i have waited 1 minute (as it said to wait up to 1 minute)
copied pasted rules from medium post that did what i wanted (still didn't work)
The allow read; without any condition in your rules is equivalent to allow read: if true;, so it allows anyone to read all data.
If you want to prevent anyone from reading the data, that'd be:
allow read: if false;
If you only want authenticated users to reading data, that'd be:
allow read: if request.auth != null;
Related
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:
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)
})
}
Right now this is the code I am using to set the custom claim called "moderator":
import firebase_admin
from firebase_admin import credentials
from firebase_admin import auth
cred = credentials.Certificate(r".\sa.json")
firebase_admin.initialize_app(cred)
EMAIL = "bearcodes#outlook.com"
user = auth.get_user_by_email(EMAIL)
uid = "H9c1PQtSHxZfV3AWW5pQjNlSDnX2" # This is for bearcodes#outlook.com (this is of course an example)
auth.set_custom_user_claims(uid, {'moderator': True})
When I run it, there are no errors and it says nothing.
My cloud firestore rules are as follows:
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /pupils/{document=**} {
allow read: if request.auth.uid != null;
}
match /cclaims/{claims} {
allow read: if request.auth.uid != null;
allow write: if request.auth.token.moderator == true;
}
}
}
However when I run this simulated test (via the console) while logged into the website, it produces the error: Error writing document: FirebaseError: Missing or insufficient permissions.
This is the "simulated test" that I ran:
function testccl() {
db.collection("cities").doc("LA").set({
name: "Los Angeles",
state: "CA",
country: "USA"
})
.then(() => {
console.log("Document successfully written!");
})
.catch((error) => {
console.error("Error writing document: ", error);
});
}
Thanks in advance and I look forward to hearing your answer(s).
-The SimsRedux Team
In your test function, write to the claims collection, you may need to reload and get a new token with the moderator claim as well.
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");*/
})
});
});
});
});
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.