Cloud Firestore:实施唯一用户名

cra*_*ter 22 firebase google-cloud-firestore

问题

我已多次看到这个问题(也在Firebase实时数据库的上下文中),但我还没有看到令人信服的答案.问题陈述相当简单:

(经过身份验证的)用户如何选择尚未使用的用户名?

首先,原因是:用户进行身份验证后,他们拥有唯一的用户ID.然而,许多网络应用程序允许用户选择"显示名称"(用户希望如何在网站上显示),以保护用户的个人数据(如真实姓名).

用户集合

给定如下的数据结构,可以为每个用户存储用户名和其他数据:

/users  (collection)
    /{uid}  (document)
        - name: "<the username>"
        - foo: "<other data>"
Run Code Online (Sandbox Code Playgroud)

但是,没有什么能阻止另一个用户(具有不同的用户{uid})将其存储name在他们的记录中.据我所知,没有"安全规则"允许我们检查name其他用户是否已经访问过.

注意:可以进行客户端检查,但作为恶意客户端的不安全可能会忽略检查.

反向映射

流行的解决方案是创建一个带有反向映射的集合:

/usernames  (collection)
    /{name}  (document)
       - uid: "<the auth {uid} field>"
Run Code Online (Sandbox Code Playgroud)

鉴于此反向映射,可以编写安全规则以强制尚未使用用户名:

match /users/{userId} {
  allow read: if true;
  allow create, update: if
      request.auth.uid == userId &&
      request.resource.data.name is string &&
      request.resource.data.name.size() >= 3 &&
      get(/PATH/usernames/$(request.resource.data.name)).data.uid == userId;
}
Run Code Online (Sandbox Code Playgroud)

并强制用户首先创建用户名文档:

match /usernames/{name} {
  allow read: if true;
  allow create: if
      request.resource.data.size() == 1 &&
      request.resource.data.uid is string &&
      request.resource.data.uid == request.auth.uid;
}
Run Code Online (Sandbox Code Playgroud)

我相信解决方案是中途的.但是,仍有一些未解决的问题.

剩余的问题/问题

这个实现已经非常复杂,但它甚至没有解决用户想要更改其用户名的问题(需要记录删除或更新规则等)

另一个问题是,没有什么能阻止用户在usernames集合中添加多个记录,有效地抢夺所有好的用户名来破坏系统.

那么问题:

  • 是否有更简单的解决方案来强制使用唯一的用户名?
  • 如何usernames防止垃圾邮件发送?
  • 如何将用户名检查设置为不区分大小写?

我尝试users使用exists()/ usernames集合的另一个规则强制存在,然后提交批量写入操作,但是,这似乎不起作用(" 缺少权限或权限不足 "错误).

另一个注意事项:我已经看到了客户端检查的解决方案.但这些都是不安全的.任何恶意客户端都可以修改代码,并省略检查.

jqu*_*lls 19

@asciimike在twitter上是一个firebase安全规则开发人员.他说,目前无法对文档中的密钥强制执行唯一性.https://twitter.com/asciimike/status/937032291511025664

由于firestore基于Google Cloud,datastore因此它继承了此问题.这是自2008年以来一直存在的长期要求 .https://issuetracker.google.com/issues/35875869#c14

但是,您可以通过使用firebase functions和一些严格来实现您的目标security rules.

您可以在媒体上查看我提出的整个解决方案. https://medium.com/@jqualls/firebase-firestore-unique-constraints-d0673b7a4952


Boh*_*ukh 14

为我创建了另一个非常简单的解决方案。

我有usernames用于存储唯一值的集合。username如果文档不存在,则可用,因此很容易在前端检查。

另外,我添加了模式^([a-z0-9_.]){5,30}$来验证键值。

使用 Firestore 规则检查所有内容:

function isValidUserName(username){
  return username.matches('^([a-z0-9_.]){5,30}$');
}

function isUserNameAvailable(username){
  return isValidUserName(username) && !exists(/databases/$(database)/documents/usernames/$(username));
}

match /users/{userID} {
  allow update: if request.auth.uid == userID 
      && (request.resource.data.username == resource.data.username
        || isUserNameAvailable(request.resource.data.username)
      );
}

match /usernames/{username} {
  allow get: if isValidUserName(username);
}
Run Code Online (Sandbox Code Playgroud)

如果用户名已存在或具有无效值,Firestore 规则将不允许更新用户的文档。

因此,仅当用户名具有有效值且尚不存在时,Cloud Functions 才会进行处理。因此,您的服务器的工作量会少得多。

云功能所需的一切就是更新usernames集合:

const functions = require("firebase-functions");
const admin = require("firebase-admin");

admin.initializeApp(functions.config().firebase);

exports.onUserUpdate = functions.firestore
  .document("users/{userID}")
  .onUpdate((change, context) => {
    const { before, after } = change;
    const { userID } = context.params;

    const db = admin.firestore();

    if (before.get("username") !== after.get('username')) {
      const batch = db.batch()

      // delete the old username document from the `usernames` collection
      if (before.get('username')) {
        // new users may not have a username value
        batch.delete(db.collection('usernames')
          .doc(before.get('username')));
      }

      // add a new username document
      batch.set(db.collection('usernames')
        .doc(after.get('username')), { userID });

      return batch.commit();
    }
    return true;
  });
Run Code Online (Sandbox Code Playgroud)