web-dev-qa-db-fra.com

Comment empêcher les propriétés utilisateur en double dans Firebase?

J'utilise FirebaseSimpleLogin pour créer des utilisateurs et gérer l'authentification.

Lorsque j'essaie de créer un nouvel utilisateur avec une connexion simple via la méthode $createUser(), firebase ne crée pas l'utilisateur si l'adresse e-mail a déjà été utilisée. Cependant, j'utilise également $set() pour enregistrer mes utilisateurs créés dans ma base de données après les avoir créés et j'utilise user.uid comme clé. Lorsque vous essayez d'écrire dans la base de données, firebase enregistre l'enregistrement même si le nom d'utilisateur n'est pas unique car seuls un e-mail et un mot de passe sont requis pour une connexion simple. Alors, comment puis-je valider un nom d'utilisateur pour qu'il soit unique lorsqu'il n'est pas utilisé comme clé de l'objet utilisateur?

Je crée de nouveaux utilisateurs comme celui-ci:

$scope.createUser = function() {
  $scope.auth.$createUser('[email protected]', '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);
    }
  });
}

Et mon objet utilisateur ressemble à ceci:

{
  "users" : {
    "simplelogin:28" : {
      "email" : "[email protected]",
      "username" : "jtrinker"
    },
    "simplelogin:30" : {
      "email" : "[email protected]",
      "username" : "jtrinker"
    }
  }
}

}

J'ai besoin d'utiliser l'uid comme clé pour chaque utilisateur, mais j'ai toujours besoin que le nom d'utilisateur soit unique.

Comment puis-je empêcher la base de données d'enregistrer des enregistrements si les propriétés d'un objet ne sont pas uniques aux propriétés d'un autre objet?

30
reknirt

Tout d'abord, si les utilisateurs ont déjà un username, c'est unique, et cela ne va pas disparaître, je vous recommande de renoncer à utiliser une simple connexion uids. Cela ne créera rien d'autre que des problèmes en essayant de basculer entre les deux, comme vous l'avez déjà découvert ici. Examinez création de vos propres jetons avec un outil comme firebase-passport-login puis stockez les enregistrements par username.

Mais puisque ce n'était pas votre question, résolvons cela pendant que nous sommes ici, car vous voudrez peut-être aller de l'avant et entrer dans la bruyante épineuse des identités doubles à travers laquelle j'ai traversé de nombreuses fois.

Pour rendre le nom d'utilisateur unique, stockez un index des noms d'utilisateur.

/users/$userid/username/$username
/usernames/$username/$userid

Pour vous assurer qu'ils sont uniques, ajoutez une règle de sécurité comme suit sur l'ID utilisateur dans les noms d'utilisateur/chemin, ce qui garantit qu'un seul utilisateur peut être attribué par nom d'utilisateur et que la valeur est l'ID de l'utilisateur:

".write": "newData.val() === auth.uid && !data.exists()"

Maintenant, assurez-vous qu'ils correspondent en ajoutant ce qui suit au nom d'utilisateur dans les utilisateurs/l'enregistrement:

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

Cela garantira que les identifiants sont uniques. Soyez prudent avec les privilèges de lecture. Vous voudrez peut-être les éviter complètement car vous ne voulez pas que quiconque recherche des e-mails privés ou des noms d'utilisateur. Quelque chose comme je l'ai démontré à l'appui pour les enregistrer serait idéal.

L'idée ici est que vous essayez d'attribuer le nom d'utilisateur et l'e-mail, s'ils échouent, alors ils existent déjà et appartiennent à un autre utilisateur. Sinon, vous les insérez dans l'enregistrement utilisateur et les utilisateurs sont désormais indexés par uid et e-mail.

Pour se conformer au protocole SO, voici le code de ce Gist, qui est mieux lu via le lien:

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);
   );   
}

Et les règles:

{
  "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"
        }
     },
  }
}
31
Kato

Ne serait-il pas plus facile d'utiliser simplement les règles de sécurité pour vérifier son existence? J'ai le mien configuré comme suit:

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

Cela permet l'écriture si le nom d'utilisateur n'existe pas. Je crois que je l'ai obtenu directement des documents Firebase.

3
Wayne Filkins