Search code examples
angularjsfirebaseangularfirefirebase-securityfirebasesimplelogin

Manage invite codes for registration with Firebase


I am teaching myself AngularJS and Firebase and am developing an app with two user roles A and B. Role B should only be able to register to the site if they have a valid invite code.

I am using Firebase as a backend and AngularFire bindings. I have set up my project with the Yeoman generator generator-angularfire. The latter provides Firebase basic password / Simple Login auth method.

My register form has more fields than just email/password. User Id, email, pass are handled internally by Firebase and stored successfully. Additionally, I save those other fields upon successful registration in another Firebase document called "accounts":

"accounts": {
    "simplelogin:16" : {
      "company" : "foo corp",
      "firstName" : "lorem",
      "lastName" : "ipsum",
      "type" : "userRoleA"
    },
    "simplelogin:19" : {
      "company" : "bar corp",
      "firstName" : "dolor",
      "lastName" : "sit",
      "type" : "userRoleA"
    },
    "simplelogin:17" : {
      "city" : "new york",
      "firstName" : "asd",
      "lastName" : "asd",
      "type" : "userRoleB"
    },
    "simplelogin:18" : {
      "city" : "washington",
      "firstName" : "a",
      "lastName" : "a",
      "type" : "userRoleB"
    }
}

Now I am trying to find out a secure way to implement the invite code feature. I have added a Firebase document called "inviteCodes":

"inviteCodes" : {
  "111" : {
    "generator" : "system"
  },
  "222" : {
    "generator" : "system"
  },
  "333" : {
    "generator" : "system"
  }
}

1 - Generate invite codes: I cannot simply put them in the Firebase directly, because in a later step I want users A to be able to generate these codes. But how can I generate codes on Firebase without exposing the method to the frontend? I would work around this by adding a Firebase rule to only grant authenticated users with type "userRoleA" inside the "accounts" document write access to the "inviteCodes" document. Is this the way to go, or is there another way? Because in this case the method to generate the codes is still exposed.

2 - Assuming 1) is solved and invite codes are in the Firebase document. Upon registration, before calling the $createUser method I need to check if the provided invite code is valid. I would query the Firebase and check if the provided code matches any of the saved invite codes. Something like this (dummy code for illustration):

var firebaseCodes = firebaseRefInviteCodes;
var providedCode = registrationForm.providedCode;
var isCodeValid = false;

for (var i = 0; i < firebaseCodes.length; i++) {
  if (firebaseCodes[i] === providedCode) {
    isCodeValid = true;
  }
}

But again, in this query all codes would be exposed to the frontend. Is there a way check if the provided code is valid without exposing all codes?

Thanks for your input.


Solution

  • In order to generate invites, I don't see any problem having frontend doing that - until it's a secure random token generator. You can also have Firebase to generate it:

    var inviteCode = firebaseRef.push().key();
    

    Security rules is a powerful way to secure your application data in Firebase. However, in order to make them work correctly you have to design your data structure in a correct way. 'Relational' DB approach will not work in this case. The problem is that userA is sharing invites only with userB and noone else must be able to 'see' them. So storing all access codes in one 'table' is not a good option. I assume that when userA creates an invite it knows for which user this invite is for. In this case you can design data structure similar to this:

    -inviteCodes
        -invitee
            -{$userB_email}
                -code: {codeValue}
                -inviterId: {userAId}
                -generator: "system"
    

    With this structure, security rules can be as follows:

    /inviteCodes/invitee - public, so userA can insert new invite code for userB
    
    /inviteCodes/invitee/{userB_email} - write new data - public, read - private, can be accessed only by userB, e.g. data.exists() && newData.child('email').val() === $userB_email && newData.child('inviteCode').val() === data.child('code').val()
    

    userA who generated the code can also store the email of the user the code has been generated for, then it can also access this data using the email and its inviterId.

    In this way you never have to retrieve invites for other users.