I have user profile data stored in Firestore. I also have some profile fields in Firestore that dictate user permission levels. I want to deny the user the ability to write or update to their Firestore profile if they include any changes that would impact their permission level.
Example fields in user's firestore doc for their profile
permissionLevel: 1
favoriteColor: red
Document ID is the same as the user's authentication uid (because only the user should be able to read / write / update their profile).
I want to deny updates or writes if the user's firestore update or write includes a permissionLevel field, to prevent the user from changing their own permission level.
Current Firestore Rules
This is working fine when I build an object in the simulator to test including or not including a field called "permissionLevel". But this is denying all update / write requests from my client-side web SDK.
service cloud.firestore {
match /databases/{database}/documents {
// Deny all access by default
match /{document=**} {
allow read, write: if false;
}
// Allow users to read, write, and update their own profiles only
match /users/{userId} {
// Allow users to read their own profile
allow read: if request.auth.uid == userId;
// Allow users to write / update their own profile as long as no "permissionLevel" field is trying to be added or updated
allow write, update: if request.auth.uid == userId &&
request.resource.data.keys().hasAny(["permissionLevel"]) == false;
}
}
}
Client-Side Function
For example, this function attempts to just update when the user was last active by updating a firestore field. This returns the error Error updating user refresh time: Error: Missing or insufficient permissions.
/**
* Update User Last Active
* Updates the user's firestore profile with their "last active" time
* @param {object} user is the user object from the login auth state
* @returns {*} dispatched action
*/
export const updateLastActive = (user) => {
return (dispatch) => {
firestore().settings({/* your settings... */ timestampsInSnapshots: true});
// Initialize Cloud Firestore through Firebase
var db = firestore();
// Get the user's ID from the user retrieved user object
var uid = firebaseAuth().currentUser["uid"];
// Get last activity time (last time token issued to user)
user.getIdTokenResult()
.then(res => {
let lastActive = res["issuedAtTime"];
// Get reference to this user's profile in firestore
var userRef = db.collection("users").doc(uid);
// Make the firestore update of the user's profile
console.log("Firestore write (updating last active)");
return userRef.update({
"lastActive": lastActive
})
})
.then(() => {
// console.log("User lastActive time successfully updated.");
})
.catch(err => {
console.log("Error updating user refresh time: ", err);
})
}
}
This same function works fine if I remove this line from the firestore rules. I don't see how they have anything to do with each other, and why it would work fine in the simulator.
request.resource.data.keys().hasAny(["permissionLevel"]) == false;