Firebase Firestore - 限制 product-documents 用户可以创建的数量

Firebase Firestore - Limit the number of product-documents users can create

我有一个 Angular web-app,使用 Firebase 作为无服务器后端技术。 web-app 当前的目的是让注册用户为提供的产品创建文档。虽然企业用户可以创建无限 product-docs,但免费用户应该只能创建一个 product-doc。

Collections:

每当用户使用

创建新的 product-doc
  public createProduct(id: string, product: Product) {
    return this.afStore.collection('products').doc(id).set(product, { merge: true });
  }

我运行一个带有触发器onCreate的云函数将创建的product-doc的id添加到相应[的productIds字段=45=]。这允许我检查 frontend-side if(!user.isBusiness && productIds.length > 1) 以限制为免费用户帐户创建多个 product-docs。

然而,我最近做了一个 pen-test 并且测试人员偶然发现,如果您在多个浏览器选项卡上打开网站并同时 运行 每个选项卡上的 createProduct() 功能同时,所有这些都将执行,允许非业务用户拥有多个 product-docs.

有没有人处理过类似的情况?我不知道是否有办法通过 Firestore 安全规则避免这种情况,或者是否需要等待活动事务完成。

在用户中创建一个 productCounter 节点。然后将您的规则与以下内容合并:

    {
      "rules": {
        "cascade_your_nodes": {
          "products": {
            "$productId" : {
              .".write": "root.child('YOUR_PATHS').child('users').child(auth.uid).child('productCounter').val() < 1 || data.exists() || root.child('YOUR_PATHS').child('users').child(auth.uid).child('isBusiness').val()"
            }
          }
        }
      }
    }

推理: 如果它不与任何现有规则冲突,则仅在以下情况下才允许写入任何 productId

  1. 请求uid节点有productCounter小于1

    root.child('YOUR_PATHS').child('users').child(auth.uid).child('productCounter').val() < 1
    
  2. 如果该节点中存在任何数据(以允许更新节点)

    data.exists()
    
  3. 如果请求 uid 节点有 isBusinesstrue

    root.child('YOUR_PATHS').child('users').child(auth.uid).child('isBusiness').val()
    

当前的实现存在竞争条件:'non-business' 用户可以创建任意数量的产品,直到将第一个 productId 写入用户文档。

在我看来,解决此问题最直接的方法是为非业务用户预填充产品 ID,并限制他们分配新产品的能力。

  • 创建非企业用户时,您 create/reserve 为他们提供一个唯一的 productId,他们可能会使用也可能不会使用。存储在用户文档中。
  • 当非业务用户创建产品时,他们只能将数据写入该产品 ID。虽然不需要修复所描述的用例,但根据您的安全要求,您可能希望通过安全规则或云功能强制执行此操作。
  • 无论多少个并发选项卡尝试创建产品,它们都将始终写入相同的 productId。
public createProduct(id: string, product: Product, user: User) {
    if (user.isBusiness) {
      return this.afStore.collection('products').doc(id).set(product, { merge: true });
    } else {
      return this.afStore.collection('products').doc(user.reservedProductId).set(product, { merge: true });
    }    
}

警告:与 any 前端代码一样,这不会阻止恶意行为者随意操纵它。如果此行为是一个安全问题,您将希望限制非业务用户对产品集合的写访问。考虑实施适当的安全规则/云功能。你只能信任后端。