首页 > 解决方案 > 如何通过 Firestore 规则检查数组类型的文档字段是否已包含值?

问题描述

给定文档类型:

interface Post {
  content: string;
  sharedBy: Array<string>;
}

要强制执行的约束是允许用户仅将其连接uidsharedBy数组一次。

此外,在我的单元测试中,在最初的位置创建了一个post.sharedBy文档[]。因此,第一次更新成功,并且 的值由下面sharedBy['1']查询确认('1'是一个测试uid)。

之后,执行完全相同的更新,但是它也成功了,而不是失败。

鉴于以下规则:

function canShare() {
  return !resource.data.sharedBy.hasAny([request.auth.uid]); // also attempted with `hasAll`
}

service cloud.firestore {
  match /databases/{database}/documents {
    match /{document=**} {
      allow read, write: if false;
    }
    match /posts/{postId} {
      allow read: if true;
      allow write: if request.auth != null && canShare();
    }
  }
}

为什么上面提到的第二次更新成功而不是失败?

编辑

以下是测试文件的内容:

import {
  initializeTestApp,
  initializeAdminApp,
  assertSucceeds,
  assertFails,
} from '@firebase/testing';

const projectId = 'projectId';
const myId = '1';
const myAuth = { uid: myId };

const getFirestore = (auth?: typeof myAuth) =>
  initializeTestApp({
    auth,
    projectId,
  }).firestore();

const getAdminFirestore = () =>
  initializeAdminApp({
    projectId,
  }).firestore();

const myPost = {
  content:
    'Lorem ipsum dolor sit amet consectetur, adipisicing elit. Minus, doloribus praesentium nulla pariatur esse laborum a in obcaecati expedita recusandae.',
  sharedBy: [],
};

const getMyPost = () => getAdminFirestore().collection('posts').add(myPost);

describe('App tests', () => {
  test("user can increase any post's share count by 1 only once", async () => {
    const db = getFirestore(myAuth);

    const { id } = await getMyPost();

    console.log((await db.collection('posts').doc(id).get()).data()!.sharedBy); // []

    await assertSucceeds(
      db
        .collection('posts')
        .doc(id)
        .update({ sharedBy: [myId] }),
    );
    console.log((await db.collection('posts').doc(id).get()).data()!.sharedBy); // ['1']
    await assertFails(
      // error
      db
        .collection('posts')
        .doc(id)
        .update({ sharedBy: [myId] }),
    );
  });
});


标签: javascriptfirebasegoogle-cloud-firestorefirebase-security

解决方案


我认为目前安全规则中没有任何方法可以强制数组只包含唯一值。虽然客户端可以通过使用运算符来确保是这种情况array-union,但恶意客户端可以添加重复条目。

在 Firestore 中确保唯一性的唯一方法是确保值是键。所以你可以制作sharedBy一个地图,其中的关键是用户的 UID。这样可以保证每个 UID 只出现一次,因为映射中的键根据定义是唯一的。

另一种选择是将sharedBy元素存储在子集合中,并且也使用 UID 作为文档 ID。这样,系统再次确保 ID 是唯一的。

最后一个选项是只允许sharedBy通过 Cloud Function 或其他一些受信任的进程更新数组,您的代码可以在其中强制执行此业务规则。


推荐阅读