0
votes

I have found various other posts that all seem to make me believe that my implementation should be working but something is still missing.

Here is my Firebase Realtime database rules:

"usernames": {
  ".read" : true,
  ".write" : "auth !== null",
  "$username":{
    ".validate": "!root.child('usernames').hasChild($username)"
  }
}

Inside my usernames node i have for example:

"usernames"
|---"johnDoe123" : "XrG34odsfla82343174094389"
|--- etc.....

I have a cloud function that I am calling by passing a token and when the cloud function runs it attempts to update the "usernames" node by adding the new requested username assuming the username is not yet taken.

Here is the portion of the function that I am using that writes to the Firebase usernames node:

let newUser = {}
newUser[req.query.username] = decoded.uid
admin.database()
 .ref('usernames')
 .update(newUser)
 .then(() => {
  console.log(`New username <${req.query.username}> added`)
   res.status(200).send(decoded)
 }).catch(e=>{
  console.log('Error', e)
 })

The cloud function is updating the usernames node but my problem is that I need to have it reject the update with the validation if another child node has the same username. As of now my rules continue to allow the update to occur despite having another child node with the same exact username which is associated to a different uid.

Any ideas what I'm doing wrong?

2
you can if the username key already exists and reject it accordingly. - Mohammed Rampurawala
Yes i have tested by using the same username as a key already existing associated with a different uid ... and when I then attempt to try updating the usernames node with the same username and a different uid it allows for the update and overwrites the existing uid stored with the username. - jremi

2 Answers

0
votes

Not sure if this is the best way, but it works for my purpose which is to check the first time a user is getting setup within my app to determine if the username being requested is already taken. If so it sends back JSON response that it was already taken. If it was not taken then it creates the record and then sends back JSON response of success..

module.exports = functions.https.onRequest((req, res) => {
  cors(req, res, () => {
    const tokenId = req.get('Authorization').split('Bearer ')[1];
    return admin.auth().verifyIdToken(tokenId)
        .then((decoded) => {
            admin.database()
            .ref('usernames')
            .child(req.query.username)
            .once('value', function(snapshot) {
                if(snapshot.val() === null){
                    let newUser = {}
                    newUser[req.query.username] = decoded.uid
                    admin.database()
                    .ref('usernames')
                    .update(newUser)
                    .then(() => {
                        console.log(`New username <${req.query.username}> added`)
                        res.status(200).send({
                            success: true,
                            message: "Username created" 
                        })
                    }).catch(err=>{
                        res.status(500).send(err)
                    })                    
                }
                else{
                    res.status(200).send({
                        success: false,
                        message: "Username already taken"
                    })
                }
            });
        })
        .catch((err) => {
            res.status(401).send(err)
        });
  })
});
0
votes

If you use the Firebase Authentication to register all users, then this system will tell the user automatically that the email address they have used to register with is already an account.

You could also make the username part of the tree path /users/AUserName so it would return null when queried