Firebase Firestore:如何知道 setDoc 是创建还是覆盖了一个文档

huangapple go评论115阅读模式
英文:

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)

huangapple
  • 本文由 发表于 2023年8月9日 17:27:21
  • 转载请务必保留本文链接:https://go.coder-hub.com/76866346.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定