0

I have a simple signup form where a user can set their details, including a username that needs to be unique.

I have written a rule to validate if the username already exists (which works) but even if the signup fails, the user account has been created.

Example signup script (stripped back):

try {
    // This creates a user on submit of the form.
    const data = await fb.auth.createUserWithEmailAndPassword(this.email, this.password)

    // Use the uid we get back to create a user document in the users collection.
    await db.collection('users').doc(data.user.uid).set({
      username: this.username, // This fails in the rule if it exists.
      firstName: this.firstName,
      lastName: this.lastName
    })
} catch (error) {
    console.log(error)
}

The call to create a user document fails because the username is not unique (which is expected), but at this point in the flow, the user has already been created in Firebase!

If they then choose another username, they can't continue because Firestore already sees a user with that same email.

Is there a better way to create this flow?

Ideally, I do not want to create a user at all if the creation of the user document fails in some way.

Thanks!

Possible solution:

I was thinking I could just immediately delete the user after they're created if the try/catch block fails:

await data.user.delete() // ...but this seems hacky?
Michael Giovanni Pumo
  • 14,338
  • 18
  • 91
  • 140
  • Have you thought about using Cloud functions? Can get some sample code if open to that approach. Could easily have a http function where you could do the query to check if the username is available and if so create the user and then put the record in firestore. – Jack Woodward Nov 23 '18 at 11:11
  • Sure, I'm using cloud functions already for other stuff...if you have an idea of how to do this with that and have sample code that'd be great. – Michael Giovanni Pumo Nov 23 '18 at 11:13

2 Answers2

2

I would recommend using Cloud Functions here probably using an http onCall one would make it nice and simple. I've not tested the below but should get you almost there.

Clientside code

const createUser = firebase.functions().httpsCallable('createUser');
createUser({
    email: this.email,
    password: this.password,
    username: this.username,
    firstName: this.firstName,
    lastName: this.lastName
}).then(function(result) {
  console.log(result); // Result from the function
  if (result.data.result === 'success') {
     await firebase.auth().signInWithEmailAndPassword(this.email, this.password);
  } else {
      console.log('Username already exists')
  }
});

Cloud function

exports.createUser = functions.https.onCall(async (data, context) => {
    const email = data.email; 
    const password = data.password;
    const username = data.username;
    const firstName = data.firstName;
    const lastName = data.lastName;

    const usersQuery = await admin.firestore().collection('users').where('username', '==', username).get();

    if (usersQuery.size > 0) {
        return {
            result: 'username-exists'
        }
    } else {
        const user = await admin.auth().createUser({
            displayName: username,
            email: email,
            password: password
        });
        await admin.firestore().collection('users').doc(user.uid).set({
            username: username,
            firstName: firstName,
            lastName: lastName
        });
        return {
            result: 'success'
        }
    }
});
Jack Woodward
  • 991
  • 5
  • 9
1

If you want a certain value to be unique, consider using it as the document ID in a collection and disallow updates to that collection.

For example, since you want user names to be unique, create a collection usernames, where the ID of each document is the user name, and the content is the UID of the user who is using that name.

Also see:

Frank van Puffelen
  • 565,676
  • 79
  • 828
  • 807
  • Yes I have this already. Checking the uniqueness of the username wasn't the issue as such...it was how to prevent the creation of a user if the rule for the username existence returns invalid. – Michael Giovanni Pumo Nov 23 '18 at 15:23
  • 1
    Aha... you'd do that by reversing the calls: first check for uniqueness, only then create the user. – Frank van Puffelen Nov 23 '18 at 15:26