System Design: inviting users to a group
How to add an invitation system to your web application with invitation mail, sending verification token and confirming the approval
As a part of our application, we wanted to add an invitation feature aimed at group admins. Users in our app can decide nowadays whether to join a group or not, different from the automatic approval in the past. In this article, I’ll describe the feature design process and how we finally executed it
Introduction
Two years passed since I started working in Check Point Software Technologies, a leading cyber security company in Israel that develops for almost three decades a versatile of security applications (such as IT security apps, network security and data security apps) that every organization needs. Following the rise of cloud applications and the transition of these security apps to the cloud, there was a demand for developing a main portal for managing and accessing the applications of the company. This new cloud infrastructure application is accessible to all Check Point customers (for example, IT admins in the organization) who manage there all their company’s users, the different groups and the users’ associated roles.
The main entities in our app include users (defined by a unique email), groups (each one has a unique name and includes several registered users) and a group admin which has admin permissions in a specific group.
It’s been a while since our security experts asked to change the existing model of adding users automatically to a group without any option to decline. To avoid the invasion of users’ privacy, we thought about an ability for group admins to add users to a group only by sending them a dedicated email. This is a significant change from the past when users were added and their details (such as name and phone) were revealed quietly to all users in a group. Therefore, we decided to develop an invitation system that can handle this situation and let the user have the option to choose whether to join the inviting group or not. The project was developed by Nadav Dor and me.
The invitation feature includes three main parts:
- inviting a user to a group (including sending invitation mail)
- accepting an invitation by the invited user
- an ability to resend invitation mail by the group’s admins.
Let’s go deep inside every stage in the invitation process. In this first post of the two-part series, I will focus on inviting users to a group by the group’s admins. The flowchart below represents the high-level design of asking a user function.
In the next paragraphs, I’ll move on to all nodes in the flowchart with informative details about every case and stage. We begin our journey when the admin fills out a form containing details of the user to add: email, name, phone and roles in the group (admin / read & write/ read-only). To avoid a security breach, the group’s admin doesn’t know whether the user is already registered to our application or not: we’re sending invitation mail for these both types of users. Different prerequisites have led to different solutions for invitation approvals of users. Handling approvals of these two types of users will be discussed in more detail in the next chapter.
Therefore, till the user’s approval, the shown details (name and phone) are the filled details by the admin (“pending data”). Only after the approval process completes, the real user’s details are revealed (in the case of registered users).
Following submitting the “adding a user” form, a request of adding a user is sent to our NodeJS microservice and the invitation process is starting to be executed. After getting the invited user’s details, we generate the invitation token (and yes, it is very hard to guess!) and save it in our DB. This hashed token cannot be decrypted (we’ll use it again when trying to validate the sent token and verify the invited user).
For sending mail to the invited users, we use the NodeMailer ² library which is a very useful and convenient tool for sending mail. Simple APIs for configuring the host, the sender and recipients, the subject and the body of the mail (in HTML format). The invitation mail is sent to the invited user with a welcome message and a request to click the link for confirmation. The link includes our frontend gateway with the token we’ve created as a query param. For example, a link can look like this:
https://sample-page.com/acceptInvitation?token=7f568ae7–2a79–44e1-a868-a3e91cb83208
From here, the process of approving the invitation is starting.
The Invitation Template
The invitation template, as you can see in the picture, includes three main parts: the invited user name (“Itay Eylon”), the inviting group’s admin (“Mike West”) and the name of the group. As I mentioned earlier, the “accept the invitation” link is a URL that includes a route to our service with the token for approval.
Database Design
For our database service, we used two tables (we are working with PostgreSQL (RDBS) as a database service). The first table is called “UsersGroups” and represents the relation between the user and group (includes both UserId and GroupId).
The second table is responsible for users who got the invitation mail and haven’t approved it yet. In the “PendingUsersGroups” table, we are saving the invited users’ pending details (name and phone), the hashed invitation token and the token’s expiration date. The table also includes the basic fields of CreatedAt and UpdatedAt (used mainly for regenerating tokens).
This part is the most sophisticated and complex in our implementation. However, once the idea is clear, the implementation is pretty simple. Only from looking at the flow chart below, you can see that there are two use cases in trying to approve an invitation for the reason that we are now dealing with both types of users to invite: registered and unregistered. From here, we define a user who hasn’t registered yet to our app as a “non-activated” user.
The flow chart is starting when the invited user clicks on the confirmation email trying to approve his invitation request. The link is redirected to an invitation page in our frontend site which is built in React framework. As a part of React lifecycle, we are sending an HTTP request (using the Axios library) for approving the user in the componentDidMount function. As you probably know, this is happening once after creating the component, which in our case it’s a feedback component with several text elements. The HTTP request includes one parameter only: the token of the user. The original token is available only for the user: it is not saved as its original value in our database, but rather a hashed version of the token, so users don’t need to worry about the possibility of revealing their tokens. Then, we started to manipulate the token.
Verification the token
Once the sent token arrived at our service, we start to operate several verifying checks. The verifying checks include interpreting the token and validating it, finding the associated user and verifying that his status is “activated”. Let’s start with the part of the validation of the sent token. We define a token as valid whether it is stand in these two conditions:
- The token (after hashing) exists in our database. We query our “PendingUserGroup” table by the hashed token and if there is a result, we keep the instance of the pending details of the user.
- The token is not expired (expiration time as we defined: seven days since the invitation was sent).
While one of these conditions is not met, we are throwing an error of “Invalid Token”. Furthermore, the error is thrown also when the user has already accepted the invitation and the token was deleted from our database. In this way, there’s no information about the user’s status in the group (very important in cases of impersonation attacks when trying to guess the token and get data about the user).
User Activation
The next part of the verification process includes getting data about the user’s activation status. As we already mentioned in the previous post, we are sending invitation links for both types of users: activated and non-activated. In this stage, we are checking whether the user is activated or not by our internal parameters (which I’m sorry about but cannot be shared in this post). If the user is not activated, we throw an error of “not activated” to the client with auto-redirect to the activation page. The client gets the activation token in the response to add the required parameters to the activation request. Otherwise, in case the user has both a valid token and activation, we can finally start the approval process.
Approve Invitation
Approving the user is the final phase in this part and it’s the most exciting one. After we found the pending data of the user (look at the validation part), we delete the pending user instance and set the reference from the UsersGroups table to null. Now, the group admin will be notified that the user has approved the invitation request and the status of the invited user will be changed from “Pending” to “Active”. The approved user will get a 200 OK response and the status page will show the congratulatory message as below.
¹ MIT License. More information on how to use the NodeMailer tool, you can find on their official site: https://nodemailer.com/about/