英文:
Firebase Firestore: How to know if setDoc created or overwrote a document
问题
使用最新版本的Firebase JavaScript SDK(10.1.0),我正在使用signInWithPopup
通过提供程序对用户进行身份验证,然后将他们的凭据保存在数据库中:
userCredentials = await signInWithPopup(auth, new GoogleAuthProvider());
await setDoc(
doc(db, "users", userCredentials.user.uid),
{
uid: userCredentials.user.uid,
email: userCredentials.user.email,
...,
},
{ merge: true },
);
我如何知道最近设置的文档是创建的(用户第一次进行身份验证,即注册到服务)还是更新的(用户已经注册过)?
附注:我希望避免查询数据库以检查现有的电子邮件,因为我需要更改访问规则并使所有用户数据对任何人都可用。
英文:
With latest version of Firebase Javascript SDK (10.1.0) I am using signInWithPopup
to authenticate users through Providers and then saving their credentials inside the DB:
userCredentials = await signInWithPopup(auth, new GoogleAuthProvider());
await setDoc(
doc(db, "users", userCredentials.user.uid),
{
uid: userCredentials.user.uid,
email: userCredentials.user.email,
...,
},
{ merge: true },
);
How can I know if the recently set document was created (user was authenticated for the first time, i.e. signed up to the service) or updated (user was already registered)?
PS: I would like to avoid querying the DB to check for existing email, as I would need to change the access rules and make all users data available to anyone.
答案1
得分: 1
我通常在这种情况下使用getDoc。在setDoc之前调用它。根据你的示例代码,我会像这样修改我的代码:
const userDocRef = doc(db, "users", userCredentials.user.uid);
const userDocSnapshot = await getDoc(userDocRef);
if (userDocSnapshot.exists()) {
// 文档存在,所以用户已经注册过了
await updateDoc(userDocRef, {
// 要更新的字段
});
} else {
// 文档不存在,所以用户是第一次注册
await setDoc(userDocRef, {
uid: userCredentials.user.uid,
email: userCredentials.user.email,
// 其他字段
});
}
如果有什么不清楚或注释不好的地方,请告诉我,大家都可以随意编辑。
英文:
I normally used getDoc for such use cases. I call it before setDoc. Based on your example code I would adapt my code like this:
const userDocRef = doc(db, "users", userCredentials.user.uid);
const userDocSnapshot = await getDoc(userDocRef);
if (userDocSnapshot.exists()) {
// Document exists, so the user was already registered
await updateDoc(userDocRef, {
// fields to update
});
} else {
// Document does not exist, so the user is signing up for the first time
await setDoc(userDocRef, {
uid: userCredentials.user.uid,
email: userCredentials.user.email,
// other fields
});
}
Let me know if something is unclear or bad commented and everyone feel free to edit.
答案2
得分: 1
判断 Firestore 数据库中是否存在一个文档的唯一方法是查询它。
作为一种解决方法,你可以使用 updateDoc()
方法向数据库写入数据,如果文档不存在,它将返回一个类型为 FirebaseError: No document to update
的错误,然后你可以在 catch()
块中使用 setDoc()
方法来创建该文档。
但请注意,这种方法每次文档不存在时都会产生一次写入操作。你需要计算并权衡哪种选项对你来说更合适(每次想要知道文档是否存在时支付一次读取操作的费用,或者在实际创建文档时支付额外的写入操作费用)。
英文:
The only way to know if a Firestore document is present in the database is to query it.
As a workaround you could use the updateDoc()
method to write to the DB and in case the doc does not already exist it will return an error of type FirebaseError: No document to update
and you would then use the setDoc()
method in a catch()
block to create it.
BUT note that this approach costs a write each time the doc does not exist. It's up to you to do the math to calculate the best option for you (pay for a read each time you want to know if the doc exists or pay for an extra write when you actually create the doc).
答案3
得分: 0
问题的关键在于安全性:为了查询数据库并知道电子邮件(或用户名)是否已存在,您必须更改Firestore的安全规则,并将对“users”集合的读取权限授予任何人:
match /users/{uid} {
allow read, write: if true
❌ 这显然是不可接受的,因为它会使系统面临未经授权的信息泄露风险。
在这篇帖子中,问题得到了很好的总结,并提供了一种解决方案(禁用客户端对Firestore的访问,并通过后端层进行传递),但是这种解决方案会使您失去使用Firestore作为客户端库和Firebase作为无服务器架构的大部分好处。
✅ 我最终成功地通过一个简单但安全的解决方法解决了这个问题。我将规则更改如下:
match /users/{uid} {
allow read, write: if request.auth.uid == uid || resource.data.email == request.auth.token.email;
这样,在用户通过身份验证提供程序(例如Google)进行身份验证后,我可以查询数据库以检查是否已存在具有该电子邮件的记录。
➕ 作为额外的奖励,当同一用户(即相同的电子邮件)通过不同的提供程序(例如Google和Linkedin)进行身份验证时,我还可以处理帐户的协调。
英文:
The point at the root of the question is security: in order to query the db and know if an email (or a username) already exists, you have to change the Firestore's security rules and give read access to the users
collection to anyone:
match /users/{uid} {
allow read, write: if true
❌ this is obviously not acceptable as it exposes the system to unauthorised information disclosure
In this post the problem is well summarised and a solution is provided (disabling client access to firestore and passing through a backend layer), however this solution makes you loose most of the benefit of using firestore as a client-side library and firebase as a server-less architecture.
✅ I have finally managed to solve the problem with a simple - yet safe - workaround. I have changed the rule as follows:
match /users/{uid} {
allow read, write: if request.auth.uid == uid || resource.data.email == request.auth.token.email;
In this way, after a user has authenticated through an Auth Provider (e.g. Google) I can query the DB to check if there is already a record with that email.
➕ As an extra bonus, I can also handle accounts reconciliation when the same user (i.e. the same email) authenticates through different Providers (e.g. Google and Linkedin)
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论