In part 1, we created a plugin that sends a text message whenever a record is created.
At this point, the text message is hard-coded to say something like ‘Record 123 was created!’ and is sent to some hard-coded phone number. The plugin would be more useful if the workspace admins could have more control over this. In this part of the tutorial, we will add in the ability for users to specify the message body and To number, as well as connect their own Twillio accounts, so they can specify their own From number.
Adding SMS Settings
First let’s add a section to the settings interface: SMS Settings.
This section will allow workspace admins to specify three things, the To number, the From number, and the content of the text message. Currently, the webhook settings are stored in https://<your-firebase>.firebaseio.com/<workspaceId>/settings/webhook. Let’s store an sms object under the settings path as well, with the following properties: to, from, and body.
Use the html below to add SMS Settings as a section in the pluginSettings form. Note the required validation on the two numbers, and some basic phone number validation on the To phone number. (The *From number can either be a phone number or an alphanumeric sender ID, so we skip validation for it.)
Adding Twillio Credentials
When you specify a From number, it needs to be a phone number or alphanumeric sender ID that has been purchased from Twillio and configured to send sms. Most likely workspace admins will try to use From numbers connected to their Twillio account, which means we need a way for them to provide the credentials for their account. So let’s add a third section to the settings interface: Twillio Credentials.
Twilio uses two credentials to determine which account an API request is coming from. The Account SID, which acts as a username, and the Auth Token which acts as a password.
Since the Auth Token should be kept private, we don’t want it to be displayed in the settings interface. So we need to update the Firebase security rules to accomodate that constraint. So far, everything has been saved in $scope.settings. These properties, such as the To and From numbers, need to be displayed by the frontend, and later accessed by the backend service, so it knows how to send the text message. So the rule to reflect that is to set the .read property to:
If auth.workspaces[$workspace] is 'admin' or 'owner' it means it is a Zengine authenticated user that can access the workspace settings section, which means they are either an admin or owner of the workspace. If the value of auth.workspaces[$workspace] is ‘server’, it means it is a backend service.
Since the Auth Token has slightly different read permissions, we can’t store it in settings. Instead, we will store it under secrets with the following read rule:
Finally, the write permissions for these properties is the same, so we can put it at the parent level. Here is what the final JSON looks like for the Firebase security rules.
Use the html below to add a Twillio Credentials section to your existing the pluginSettings form. Note the help text for the Auth Token reflects these new security rules.
Now that we are saving the Auth Token to https://<your-firebase>.firebaseio.com/<workspaceId>/secrets, we need to update our fronted plugin.js code.
First, we must modify the $scope.connect method to fetch a reference to the secrets path in Firebase and set it as a scope property. Note we can’t use the $asObject method because we don’t have read permission.
Now we need to modify the $scope.updatedFirebaseData method to use the $update method to save the Auth Token to Firebase if passed. For more information, check out the AngularFire documention on $update.
Using Firebase in Your Backend Service
Now that we are saving these SMS and Twillio Settings in Firebase, we need to update our backend service code to use these new settings.
If a frontend component of the plugin was making requests to the backend service, we could send the Firebase settings as part of the payload. However, since a webhook is making the request to the backend service with a predetermined payload, the backend service needs to fetch these settings from Firebase directly.
In order to do this, we first need to include the zn-firebase library, which when invoked, returns a reference to a Firebase object constructed from the Firebase URL associated with your plugin. If you are developing locally, you will need to pass in the Firebase URL and secret as the headers X-Firebase-Url and X-Firebase-Secret, respectively.
After including the zn-firebase library, we can now fetch the data from Firebase. Even though the backend service has read access to both /<workspaceId>/settings and /<workspaceId>/secrets, we can’t make a single request to the parent object /<workspaceId>. This is because of two facts about how Security and Firebase Rules work: 1.) Rules are not filters and 2.) Rules cascade. In other words, since we didn’t define a read rule at the /<workspaceId> level (or any parent), we can’t read at that location (rules are not filters). And we didn’t define a read rule at the parent /<workspaceId> level because we have slightly different read rules for the two children, and any rules defined at the child level will be ignored if already defined at the parent level (rules cascade). Therefore, we need to make two separate Firebase requests: one for /<workspaceId>/settings and one for /<workspaceId>/secrets.
After fetching the data from Firebase, we can update the sendSms function by replacing the hard-coded values with the ones from Firebase. The full backend plugin.js code is below.
Right now, your backend plugin endpoint is completely public, and anyone that makes a request to it can trigger sending of text messages. If you want to ensure that only the associated webhook can send text messages, we can take advantage of a webhook’s unique secretKey, which is sent in the X-Zengine-Webhook-Key header for every request made by a webhook. The secretKey is a returned with the payload when you first create the webhook. So let’s save this secret key to Firebase, and verify it against the header in the backend service code.
In the frontend plugin.js code below, we modified the $scope.save method slightly to save the secretKey to Firebase. We save it to the secrets object, so the frontend plugin code has permission to update it, but not read it.
Now in our service code, we modified the sendSms method slightly to compare the header in the request with the secretKey in Firebase and return a 401 if they don’t match.
Your plugin should now be able to allow workspace admins to connect their own Twillio accounts, and specify the text message body, and to/from numbers. The backend service
The code for the entire record sms plugin can be found below and also on Github. In this case, the plugin namespace is ‘namespaced’, so to make it work as your own, you will need to replace all instances of the word ‘namespaced’ with your namespace.
If you have improvements to the plugin, feel free to make pull requests to the code repository and update the documentation for it here.