To meet the requirement, "query if a user with a specific email exists in the database WITHOUT giving access to the whole users collection," you'll need to rethink your database architecture. Firestore won't allow you to make queries on data that you don't also have read access to.
I would recommend creating a separate collection that just contains email addresses in use like this:
{
"emails": {
"jane@example.com": { userId: "abc123" },
"sally@example.com": { userId: "xyz987" },
"joe@example.com": { userId: "lmn456" }
}
}
This collection should be updated everytime a user is added or an email address is changed.
Then set up your Firestore rules like this:
service cloud.firestore {
match /databases/{database}/documents {
match /emails/{email} {
// Allow world-readable access if the email is guessed
allow get: if true;
// Prevent anyone from getting a list of emails
allow list: if false;
}
}
}
With all of that you can then securely allow for anonymous queries to check if an email exists without opening your kimono, so to speak.
List All Emails
firebase.firestore().collection('emails').get()
.then((results) => console.error("Email listing succeeded!"))
.catch((error) => console.log("Permission denied, your emails are safe."));
Result: "Permission denied, your emails are safe."
Check if joe@example.com exists
firebase.firestore().collection('emails').doc('joe@example.com').get()
.then((node) => console.log({id: node.id, ...node.data()}))
.catch((error) => console.error(error));
Result: {"id": "joe@example.com": userId: "lmn456"}
Check if sam@example.com exists
firebase.firestore().collection('emails').doc('sam@example.com').get()
.then((node) => console.log("sam@example.com exists!"))
.catch((error) => console.log("sam@example.com not found!"));
Result: sam@example.com not found!