使用 Firebase simplelogin 强制使用唯一的用户名

Enforcing unique usernames with Firebase simplelogin

我最近学习了关于 Thinkster 的教程,使用 Angular 和 Firebase 创建网络应用程序。

本教程使用 Firebase simpleLogin 方法允许创建包含用户名的 'profile'。

工厂:

app.factory('Auth', function($firebaseSimpleLogin, $firebase, FIREBASE_URL, $rootScope) {
var ref = new Firebase(FIREBASE_URL);


var auth = $firebaseSimpleLogin(ref);

var Auth = {
    register: function(user) {
       return auth.$createUser(user.email, user.password);
    },
    createProfile: function(user) {
        var profile = {
            username: user.username,
            md5_hash: user.md5_hash
        };

        var profileRef = $firebase(ref.child('profile'));
        return profileRef.$set(user.uid, profile);
    },
    login: function(user) {
        return auth.$login('password', user);
    },
    logout: function() {
        auth.$logout();
    },
    resolveUser: function() {
        return auth.$getCurrentUser();
    },
    signedIn: function() {
        return !!Auth.user.provider;
    },
    user: {}
};

$rootScope.$on('$firebaseSimpleLogin:login', function(e, user) {
    angular.copy(user, Auth.user);
    Auth.user.profile = $firebase(ref.child('profile').child(Auth.user.uid)).$asObject();

    console.log(Auth.user);
});
$rootScope.$on('$firebaseSimpleLogin:logout', function() {
    console.log('logged out');

    if (Auth.user && Auth.user.profile) {
        Auth.user.profile.$destroy();
    }
    angular.copy({}, Auth.user);
});

return Auth;
});

控制器:

$scope.register = function() {
    Auth.register($scope.user).then(function(user) {
        return Auth.login($scope.user).then(function() {
            user.username = $scope.user.username;
            return Auth.createProfile(user);
        }).then(function() {
            $location.path('/');
        });
    }, function(error) {
        $scope.error = error.toString();
    });
};

在教程的最后有一个 'next steps' 部分,其中包括:

Enforce username uniqueness-- this one is tricky, check out Firebase priorities and see if you can use them to query user profiles by username

我搜索了又搜索,但找不到关于如何执行此操作的明确解释,尤其是在 FirebasesetPriority() 功能方面

我是 Firebase 的新手,所以如果能提供任何帮助,我们将不胜感激。

有一些 similar questions,但我似乎不知道如何解决这个问题。

非常感谢。


编辑

根据 Marein 的回答,我已将控制器中的注册功能更新为:

$scope.register = function() {

    var ref = new Firebase(FIREBASE_URL);
    var q = ref.child('profile').orderByChild('username').equalTo($scope.user.username);
    q.once('value', function(snapshot) {
        if (snapshot.val() === null) {
            Auth.register($scope.user).then(function(user) {
                return Auth.login($scope.user).then(function() {
                    user.username = $scope.user.username;
                    return Auth.createProfile(user);
                }).then(function() {
                    $location.path('/');
                });
            }, function(error) {
                $scope.error = error.toString();
            });
        } else {
            // username already exists, ask user for a different name
        }
    });
};

但它在行 var q = ref.child('profile').orderByChild('username').equalTo($scope.user.username); 中引发了 'undefined is not a function' 错误。之后我注释掉了代码并尝试了 console.log(q) 但仍然没有快乐。

编辑 2

上述问题是 Thinkster 教程使用 Firebase 0.8 并且 orderByChild 仅在更高版本中可用。已更新,Marein 的回答很完美。

这里有两件事要做,客户端检查和服务器端规则。

在客户端,您想检查用户名是否已经存在,以便在将其发送到服务器之前告诉用户他们的输入无效。具体在哪里实施取决于您,但代码看起来像这样:

var ref = new Firebase('https://YourFirebase.firebaseio.com');
var q = ref.child('profiles').orderByChild('username').equalTo(newUsername);
q.once('value', function(snapshot) {
  if (snapshot.val() === null) {
    // username does not yet exist, go ahead and add new user
  } else {
    // username already exists, ask user for a different name
  }
});

您可以使用它在写入服务器之前进行检查。但是,如果用户是恶意的并且决定使用 JS 控制台写入服务器怎么办?为了防止这种情况,您需要服务器端安全性。

我试图想出一个示例解决方案,但我 运行 遇到了问题。希望有知识渊博的人出现。我的问题如下。假设您的数据库结构如下所示:

{
  "profiles" : {
    "profile1" : {
      "username" : "Nick",
      "md5_hash" : "..."
    },
    "profile2" : {
      "username" : "Marein",
      "md5_hash" : "..."
    }
  }
}

添加新配置文件时,您需要制定规则以确保不存在具有相同 username 属性 的配置文件对象。但是,据我所知,Firebase 安全语言不支持这种数据结构。

一种解决方案是更改数据结构以使用 username 作为每个配置文件的键(而不是 profile1profile2、...)。这样一来,自动只能有一个具有该用户名的对象。数据库结构为:

{
  "profiles" : {
    "Nick" : {
      "md5_hash" : "..."
    },
    "Marein" : {
      "md5_hash" : "..."
    }
  }
}

在这种情况下,这可能是一个可行的解决方案。但是,如果不仅用户名,而且电子邮件也必须是唯一的怎么办?它们不能都是对象键(除非我们使用字符串连接...)。

我想到的另一件事是,除了配置文件列表之外,还要保留一个单独的用户名列表和一个单独的电子邮件列表。然后可以在安全规则中轻松使用这些规则来检查给定的用户名和电子邮件是否已经存在。规则看起来像这样:

{
  "rules" : {
    ".write" : true,
    ".read" : true,
    "profiles" : {
      "$profile" : {
        "username" : {
          ".validate" : "!root.child('usernames').child(newData.val()).exists()"
        }
      }
    },
    "usernames" : {
      "$username" : {
        ".validate" : "newData.isString()"
      }
    }
  }
}

然而现在我们运行陷入了另一个问题;如何确保在创建新配置文件时,用户名(和电子邮件)也被放入这些新列表中? [1]

这又可以通过将配置文件创建代码从客户端中取出并将其放在服务器上来解决。然后客户端需要请求服务器创建一个新的配置文件,服务器将确保执行所有必要的任务。

然而,似乎我们在回答这个问题上走得太远了。也许我忽略了一些事情,事情比看起来更简单。任何想法表示赞赏。

此外,如果这个答案更像是一个问题而不是答案,我深表歉意,我是 SO 的新手,还不确定什么是合适的答案。

[1] 尽管您可能会争辩说这不需要确保,因为恶意用户只会通过不声明其唯一身份来伤害自己?

我遇到了类似的问题。但这是在用密码和电子邮件注册用户之后。在用户配置文件中可以保存一个必须唯一的用户名,我找到了解决方案,也许这可以为您服务。

Query for username unique in Firebase

        var ref = new Firebase(FIREBASE_URL + '/users');

        ref.orderByChild("username").equalTo(profile.username).on("child_added", function(snapshot) {
            if (currentUser != snapshot.key()) {
                scope.used = true;
            }   
        }); 

        ref.orderByChild("username").equalTo(profile.username).once("value", function(snap) {
            //console.log("initial data loaded!", Object.keys(snap.val()).length === count);
            if (scope.used) {
                console.log('username already exists');
                scope.used = false;
            }else{
                console.log('username doesnt exists, update it');
                userRef.child('username').set(profile.username);
            }   
        }); 
    };