Firebase中如何防止重复用户属性?

34

我正在使用 FirebaseSimpleLogin 创建用户并处理身份验证。

当我尝试通过 $createUser() 方法使用简单登录创建新用户时,如果电子邮件地址已被使用,Firebase 将不会创建用户。但是,在创建用户后,我还使用 $set() 将它们保存到我的 Firebase 中,并将 user.uid 用作键。在尝试写入数据库时,即使用户名不唯一,Firebase 也会保存记录,因为简单登录只需要电子邮件和密码。那么,当用户名未用作用户对象的键时,如何验证用户名是唯一的?

我像这样创建新用户:

$scope.createUser = function() {
  $scope.auth.$createUser('trinker@gmail.com', 'password').then(function(user, err) {
    if (!err) {
      ref.child('users/' + user.uid).set({
        email: user.email,
        username: user.username
      });
      console.log("success!");
    }else{
      console.log(err.message);
    }
  });
}

我的用户对象长这样:

{
  "users" : {
    "simplelogin:28" : {
      "email" : "trinker@gmail.com",
      "username" : "jtrinker"
    },
    "simplelogin:30" : {
      "email" : "test@gmail.com",
      "username" : "jtrinker"
    }
  }
}

}

我需要将每个用户的uid作为键,但仍需要用户名是唯一的。

我如何防止Firebase在一个对象中的属性与另一个对象中的属性不唯一时保存记录?


2
如果用户未成功创建帐户,为什么要编写用户记录?未经身份验证的用户为什么要编写记录?您能详细说明这是个好主意的原因吗? - Kato
1
我想我表达不够清楚。显然,未经身份验证的用户编写记录是一个非常糟糕的想法,正如你所明确暗示的那样。然而,重点不在于此。问题是,由于创建用户帐户不需要用户名,因此我该如何确保我的Firebase中没有重复的用户名? - reknirt
您的问题是如何使电子邮件地址唯一。如果该电子邮件地址已被使用,用户将无法创建帐户,因此也无法进行身份验证,这将防止为重复的电子邮件地址创建用户帐户。因此,我仍然没有完全理解我们试图解决的更大用例。 - Kato
非常抱歉造成困惑。在我的问题结尾处,我提到我还需要用户拥有用户名,这个用户名也需要是唯一的。是的,所有的电子邮件地址都将是唯一的,因为如果电子邮件已经被使用,他们将无法创建帐户。然而,Firebase简单登录不需要用户名,因此某人可以使用唯一的电子邮件地址创建新用户,但输入重复的用户名。我想在上面的用户对象中添加一个用户名键,但我需要确保除非该用户名在我的Firebase所有用户中也是唯一的,否则不能写入用户。 - reknirt
我编辑了我的问题,希望更加清晰明了。 - reknirt
2个回答

35
首先,如果用户已经有一个{username},它是唯一的,并且不会消失,我建议您放弃使用简单的登录{uid},这样只会在两者之间来回切换时创建问题,正如您在这里已经发现的那样。可以考虑使用firebase-passport-login这样的工具,通过creating your own tokens来存储记录 {username}。
但既然这不是您的问题,我们就在这里解决一下,因为您可能想进入我已经通过多次的双重身份的荆棘丛中。
要使{username}唯一,请存储用户名索引。
/users/$userid/username/$username
/usernames/$username/$userid

为确保它们是唯一的,请在用户名/路径中的用户ID上添加以下安全规则,以确保每个用户名只能分配一个用户,并且该值为用户的ID:
".write": "newData.val() === auth.uid && !data.exists()"

现在我们需要确保它们匹配,可以通过在用户记录(users/)中的用户名(username)后面添加以下内容来实现:

"users": {
   "$userid": {
      "username": {
         ".validate": "root.child('usernames/'+newData.val()).val() === $userid"
      }
   }
}

这将确保ID是唯一的。小心阅读权限。你可能想完全避免它们,因为你不希望任何人查找私人电子邮件或用户名。像我在支持中演示的那样,保存这些内容是理想的。
这里的思路是,你尝试分配用户名和电子邮件,如果失败,则它们已经存在并属于另一个用户。否则,你将它们插入到用户记录中,现在已经通过uid和电子邮件索引了用户。
为了遵守SO协议,这是来自那个gist的代码,最好通过链接阅读:
var fb = new Firebase(URL);

function escapeEmail(email) {
   return email.replace('.', ',');
}

function claimEmail(userId, email, next) {
   fb.child('email_lookup').child(escapeEmail(email)).set(userId, function(err) {
      if( err ) { throw new Error('email already taken'); }
      next();
   });
}

function claimUsername(userId, username, next) {
   fb.child('username_lookup').child(username).set(userId, function(err) {
      if( err ) { throw new Error('username already taken'); }
      next();
   });   
}

function createUser(userId, data) {
   claimEmail(userId, data.email, claimUsername.bind(null, userId, data.username, function() {
      fb.child('users').child(userId).set(data);
   );   
}

还有规则:

{
  "rules": {
     "users": {
        "$user": {
           "username": {
               ".validate": "root.child('username_lookup/'+newData.val()).val() === auth.uid"
           },
           "email": {
               ".validate": "root.child('email_lookup').child(newData.val().replace('.', ',')).val() === auth.uid"
           }
        }
     },

     "email_lookup": {
        "$email": {
           // not readable, cannot get a list of emails!
           // can only write if this email is not already in the db
           ".write": "!data.exists()",
           // can only write my own uid into this index
           ".validate": "newData.val() === auth.uid"
        }
     },
     "username_lookup": {
        "$username": {
           // not readable, cannot get a list of usernames!
           // can only write if this username is not already in the db
           ".write": "!data.exists()",

           // can only write my own uid into this index
           ".validate": "newData.val() === auth.uid"
        }
     },
  }
}

email_lookup和username_lookup只是包含用户名->用户ID和电子邮件->用户ID的哈希表。 - Kato
我想更具体地问一下,email_lookupusername_lookup是什么?它们不像“rules:”下的其他项目那样是数据。它们作为函数使用吗? - reknirt
我收到了“没有这样的方法/属性‘replace’”的错误信息,但规则中有newData.replace。这个在安全规则中可用吗? - Douglas Correa
文档的快速检查可以确认或否认其存在。在这种情况下,这只是一个打字错误;应该是 newData.val().replace()(记住它适用于字符串)。 - Kato
6
我在这里看到的唯一问题是它允许用户声明多个用户名/电子邮件 - 他们不能同时使用几个,但他们可以防止其他用户使用它们。但似乎没有更好的解决方案,除非使用自定义令牌。 - Gustavo Bicalho
显示剩余5条评论

2

使用安全规则检查它的存在不是更容易吗?我已将我的设置如下:

"usernames": {
  "$usernameid": {
    ".read": "auth != null",
        ".write": "auth != null  && (!data.exists() || !newData.exists())"
    }
    }

如果用户名不存在,这将允许写入。我相信这直接来自Firebase文档。

2
请问$usernameid是什么?因为我刚开始使用Firebase,从文档中也没有理解它的含义。 - Arif Nouman Khan

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接