Chapter 15. Deployment, testing, and security

This chapter covers

  • Deploying Brushfire to a PaaS
  • Distinguishing between development and production
  • Incorporating testing into Brushfire
  • Understanding XSS and CSRF attacks and protection

In this final chapter, we’ll address deploying Brushfire into the wild. This will require us to choose a deployment destination, and will include deploying and configuring a remote PostgreSQL instance for our main database and a Redis instance for our session and WebSocket stores. We’ll separate Brushfire into three different environments: development, production, and test. We’ll use the environments as a way to configure Brushfire separately based on one of the three environments chosen. Testing is a vital step in the development process. We’ll show you how to set up tests for endpoints and model methods. We’ll wrap up the chapter with a discussion of the most prevalent security vulnerabilities and steps you can take to protect against them.

15.1. Obtaining the example materials for this chapter

If you’ve followed along in chapter 14 with an existing project, you can continue to use that project in this chapter. But if you want to start from this chapter and move forward, clone the following repo: https://github.com/sailsinaction/brushfire-ch14-end. After cloning the repo, install the Node module dependencies via npm install. You’ll also want to add the local.js file that you created in chapter 14. In Sublime, copy and paste your local.js file that you created in chapter 14, or create a new file in brushfire/config/local.js, and add the following code.

Example 15.1. Adding to the local.js file
module.exports.blueprints = {
  shortcuts: true,
  prefix: '/bp',
};

module.exports.connections = {
  myPostgresqlServer: {
    adapter: 'sails-postgresql',
    host: 'localhost',
    database: 'brushfire'
  }
};

module.exports.mailgun =  {
  apiKey: 'ADD YOUR MAILGUN API KEY HERE',
  domain: 'ADD YOUR MAILGUN DOMAIN HERE',
  baseUrl: 'http://localhost:1337'
};

15.2. Deploying your Sails app

You have many options when deploying a Sails application into the wild. A significant choice is whether to deploy the application to a server located on your own hardware, on a cloud computing service (CCS), or to one of the many platform as a service (PaaS) providers. Creating your own server, whether locally or virtually, is beyond the scope of this book. Instead, we’ll use a PaaS called Heroku to deploy Brushfire. The techniques we’ll employ, however, will be generally applicable to any PaaS you choose. We’ll also use Heroku to host remote versions of our existing PostgreSQL database and new databases for the session and WebSockets databases. In this section, we’ll create destinations for Brushfire and our main PostgreSQL database on Heroku.

15.2.1. About Heroku

Like other PaaS providers, Heroku frees you from having to manage much of the infrastructure involved in deploying and maintaining your application. This infrastructure includes the hardware and software related to servers, storage, and networking. Heroku uses lightweight Linux containers called dynos to run instances of an application.

Definition

A container is part of a technology generally termed containerization, where multiple isolated instances of an operating system can run on a single shared host. Each container can, in turn, run individual instances of an application.

15.2.2. Scaling to multiple dynos

For many applications, one server is enough to handle the expected traffic—at least at first. Chad is thinking big, so we’re planning for growth. We’ll assume Brushfire could be deployed on multiple containerized dynos similar to figure 15.1.

Figure 15.1. Horizontal scaling of an application on Heroku is accomplished by adding additional dynos to a given project.

Adding dynos in this way is known as horizontal scaling. This is opposed to adding resources to an individual node or dyno, known as vertical scaling. Horizontal scaling has the advantage that (in principle) you aren’t limited by the maximum amount of resources you can add to a single machine. Instead, the load can be spread across multiple machines/dynos. You also have the advantage of reducing the risk of a single source of failure. Configuring Brushfire so that it can be successfully deployed using horizontal scaling on multiple dyno instances requires a few application configuration considerations, including the following:

  • How do you route incoming requests to multiple dynos?
  • Where will your database reside, and how will each dyno be configured to connect to it?
  • How do you implement a centralized storage regime for WebSockets and sessions-?

First, Heroku has its own load-balancing router that sits between an incoming request and the dyno instances. Incoming requests are automatically routed for you similar to figure 15.2.

Figure 15.2. Heroku’s load-balancing router automatically routes incoming requests to multiple instances of Sails within multiple dynos.

It’s also worth pointing out that the traffic from the browser to this load balancer is using HTTPS. Therefore, you don’t need to worry about configuring TLS. We’ll discuss TLS in the security section later in this chapter.

Definition

TLS stands for transport layer security and is responsible for encrypting traffic between the browser and server. Its purpose is to ensure that traffic between the browser and server remains private.

Second, for your database, you’ll have one remote PostgreSQL instance that covers all current and subsequent dynos. You’ll also move your sessions and WebSockets store from an individual per-instance memory store to one central Redis location, similar to figure 15.3.

Figure 15.3. The Brushfire dynos will use a centralized PostgreSQL instance for the main database and a Redis instance for session and WebSocket storage.

Definition

Redis is an in-memory datastore using key/value pairs similar to a dictionary. We’ll take a much closer look at Redis later in the chapter.

The goal here is to make each dyno stateless and therefore independent of holding things in memory like a session id. But before you transition your PostgreSQL database, session, and WebSocket store, you’ll create an initial Heroku dyno for Brushfire. The remaining configuration steps involve setting up instructions for Heroku and Sails to follow when each dyno is created.

15.2.3. Installing the Heroku tool belt

Heroku uses Git to actually deploy Brushfire. Therefore, you’ll need to create another remote that’s pointing to Heroku.

Definition

A remote is the label for the destination (URL) of a remote repository.

Instead of using Git directly from the command line, Heroku has its own tool belt that wraps Git functionality. We assume that you’ve already created a free account on http://heroku.com. Once you’ve created the account, you’ll want to install the Heroku tool belt found here: https://toolbelt.heroku.com/. After the tool belt is installed, head over to the command line, and from the root of your Brushfire project type

If this is the first time you’ve used the Heroku tool belt from the command line, you’ll be prompted for your Heroku account credentials. After you enter your credentials, a Heroku application will be created with an alias to a remote Heroku repo:

~/brushfire $ heroku create
https://still-retreat-63077.herokuapp.com/ | https://git.heroku.com/still-
 retreat-63077.git
~/brushfire $

You now have a place for Brushfire, but you haven’t pushed anything yet, so the Brushfire application has not, as yet, been deployed.

Heroku will provide a random name for your application. For example, it named ours still-retreat-63077, which doesn’t exactly roll off your tongue. You can change the name by navigating your browser to the Heroku dashboard and selecting the randomly assigned name. Select the Settings option, similar to figure 15.4.

Figure 15.4. You can rename an application directly from the Heroku dashboard under Settings .

Note

Heroku application names must be unique, so your application name will be different from ours.

Heroku application names are unique. We suggest using brushfire in the name, followed by some other identifier. For example, we renamed our application brushfire-sailsinaction. To change the name, click the pencil icon next to the name in the dashboard. Changing the name of the application does have an implication with respect to the Heroku remote repo. To review, Brushfire will be using three Git repositories, as illustrated in figure 15.5.

Figure 15.5. Brushfire is stored in a local GIT repo on your local machine with two remote repos, one each on Heroku and GitHub .

We have our local repo on our machine and a remote repo on GitHub. When we created an application on Heroku, another remote repository was created pointing to our application still-retreat-63077. Therefore, we need to change the name of the remote repository. In our case, we renamed it from still-retreat-63077 to brushfire-sailsinaction. Rename the application using the unique name you created earlier. Heroku makes this easy to do from the command line:

You’ve created a place for Brushfire on Heroku, but before you actually deploy it, you also need to create a place for your PostgreSQL database.

15.2.4. Using a remote PostgreSQL database

During development, you used a local running version of PostgreSQL as your main database for Brushfire. This works for development. But now that you’re transitioning to a production environment, you need a more stable place to store your data. Your PaaS provider, Heroku, also provides remote PostgreSQL instances as an add-on database as a service (DaaS). This will allow you to separate the location of your application from the underlying database. To add the service, return to Heroku and select your application from the dashboard, and then select Find More Add-ons, similar to figure 15.6.

Figure 15.6. Heroku offers a variety of add-ons as a service, including PostgreSQL. To obtain a list of all services, click Find More Add-ons on the dashboard.

After clicking the link, you’ll go through several steps, as illustrated in figure 15.7.

Figure 15.7. Locate and click Heroku Postgres from the list of available services. Click Login to Install , and then Install Heroku Postgres . You’ll want to choose the zero-cost Hobby Dev and click the Provision button. Finally, you’ll see the Heroku Postgres instance .

Heroku has created a PostgreSQL instance that will serve as your production database. More on what production means in a moment. Now that you have destinations for the application and database, you need to provide the configuration instructions that Heroku and Sails will use each time Brushfire is deployed and started.

15.3. Using environment variables with Sails

Environment variables are values kept in memory that can be accessed by a particular running instance of Sails. They’re useful for temporarily storing sensitive credentials to databases and services like Mailgun. They can also be used as a way of configuring Sails when the Sails server lifts. In this section, we’ll show you how to use environment variables with Heroku to configure and store credentials for your remote PostgreSQL database, as depicted in figure 15.8.

Figure 15.8. When Heroku starts the dyno, an environment variable is created in memory . When Sails starts , how do you get the value of the environment variable into Sails?

We’ll also show you the mechanism for accessing environment variables within Sails. As always, instead of discussing these concepts theoretically, we’ll dive in with an example.

15.3.1. Storing credentials in environment variables

Using brushfire/config/local.js during development was a convenient way of storing credentials locally. That, combined with using brushfire/.gitignore, prevented the local.js file from being pushed to your remote repository, thereby effectively preventing sensitive data from being pushed to a public repository. Now that you’re deploying Brushfire to Heroku, there’s an issue of where and how to store these credentials. You’ll store the credentials on Heroku and access them securely in Sails using environment variables.

Note

Remember that environment variables are values that are stored in memory and can be accessed by a particular running instance of Sails.

When you created the PostgreSQL instance on Heroku, a configuration variable named DATABASE_URL was generated for you. Configuration variable is Heroku’s term for a potential environment variable. We say potential because it doesn’t become an environment variable until the dyno is started and the configuration variables are loaded into the environment’s memory. The value for the DATABASE_URL environment variable contains the credentials for your remote PostgreSQL database instance outlined in figure 15.9.

Figure 15.9. The value of the DATABASE_URL contains the database credentials including the user , password , host , port , and database to the PostgreSQL instance.

If you’re curious where to find the configuration variables set by Heroku, click the Settings menu of your Brushfire application in Heroku, and then click Reveal Config Vars. Your browser should look similar to figure 15.10.

Figure 15.10. Heroku allows you to create environment variables, which are set prior to launching Brushfire. When you create the PostgreSQL instance, the DATABASE_URL config var is also created.

Once you deploy Brushfire to Heroku, this DATABASE_URL can be used within Sails to configure the connection to the remote PostgreSQL instance. At this point, you have a remote PostgreSQL database and the necessary credentials in an environment variable that you can access. But Sails has only a connection to your local PostgreSQL instance. Let’s create a separate connection for the remote database instance.

15.3.2. Configuring a connection to a remote database

You’re currently using a connection named myPostgresqlServer, defined in brushfire/config/local.js, to connect your local Brushfire application to your local Postgre-SQL database, similar to the following listing.

Example 15.2. Credentials to your local PostgreSQL database

Let’s create another connection named productionPostgresqlServer to connect your soon-to-be deployed version of Brushfire to your remote PostgreSQL database on Heroku. In Sublime, open brushfire/config/connections.js, and add the following connection.

Example 15.3. Adding a connection to the remote Heroku PostgreSQL instance

Notice that you reference the DATABASE_URL environment variable that holds the remote PostgreSQL credentials using Node’s process dictionary to access it when Sails starts. Let’s take a closer look at using the process dictionary as a conduit to access environment variables.

15.3.3. Accessing environment variables in custom code

In the previous section, you created a new connection to your remote PostgreSQL instance and used the DATABASE_URL environment variable that Heroku sets when Brushfire is deployed to access the database credentials. You’ll access the environment variable using the global process dictionary of Node. Let’s examine the process dictionary in action. From the command line, start the Sails REPL by typing

Definition

The read-eval-print loop (REPL) is a program that allows you to interactively execute JavaScript and immediately see the results within a running Node application in the Sails console. It functions similarly to executing a command in the console of your browser on the frontend.

To log the contents of the process dictionary, return to the command line and type

There’s quite a bit of information attached to the process dictionary, so you can make it more manageable by looking at a portion of the process dictionary by next typing

You should see the DATABASE_URL as one of the environment variables logged to the terminal window. When Heroku starts an instance of Sails, it also sets the DATABASE_URL environment variable automatically. Now, configure Brushfire to use a connection to the local PostgreSQL instance while you’re developing on your local machine. Use the new remote connection configuration when you deploy Brushfire to Heroku using Sails runtime modes. To exit the Sails REPL, press Ctrl-C twice.

15.4. Runtime environments

Currently, when you start Brushfire using sails lift, you initiate a runtime environment in development mode.

Definition

A runtime environment loosely refers to the computer or container in which an application is currently running.

A runtime mode enables you to configure Sails based on a mode by setting the NODE_ENV environment variable.

Definition

NODE_ENV is the holy grail of environment variables for third-party Node applications. Many Node applications, including Sails, use NODE_ENV to set up the initial runtime mode of an application.

The different configurations based on the mode are accomplished through configuration files in brushfire/config/env/. Sails creates two runtime-specific files with each new project: development.js and production.js. These files work like the other Sails configuration files found in brushfire/config/. The settings in these files, however, override any other settings in brushfire/config/ (except for brushfire/config/local.js). In addition to being able to configure Sails differently based on the mode through the contents of these environment-specific files, development and production modes also have special significance in Sails. In development mode, your Sails app will go out of its way to help you. For example, Sails will provide you with more-descriptive error and debugging output. In contrast, production mode configures itself (and its dependencies) to optimize performance.

You have a variety of ways to set the runtime mode when Sails starts:

  • Using sails lift without any command-line parameters or environment variables will set Sails’ runtime mode to development.
  • Using sails lift --prod sets Sails’ runtime mode to production.
  • Using NODE_ENV=production node app.js sets Sails’ runtime mode to production.
  • Setting the environment variable NODE_ENV=production in the terminal window before starting Sails will set Sails’ runtime mode to production.

To ensure that Sails will run in production mode when deployed to Heroku, we’ll create a Heroku config variable that will set NODE_ENV to production when a dyno starts. You can create the variable from the terminal window by typing the following commands on the command line:

~/brushfire $ heroku config:set NODE_ENV=production
Note

We could have created the config variable from within the Heroku dashboard. Earlier, we showed you how to access DATABASE_URL from Heroku config variables. You can also add variables directly from the Heroku user interface instead of the command line.

Heroku will now set NODE_ENV to production each time Sails is deployed and before Sails starts up. Now that you can set Sails’ runtime mode, let’s instruct Sails to execute particular commands based on a particular mode.

15.4.1. Setting a default datastore for production

When Sails starts, all models are currently configured to use the local PostgreSQL instance connection in brushfire/config/models.js, similar to the next listing.

Example 15.4. Setting the default connection for all models

You can override brushfire/config/models.js with another configuration file found at brushfire/config/env/production.js.

Note

Recall that brushfire/config/env/production.js is executed if Sails is set to production mode via the NODE_ENV environment variable.

From this file, you can set the default connection for all models when Sails is in production mode. In Sublime, open brushfire/config/env/production.js, and add a new models property that uses the productionPostgresqlServer connection, similar to this.

Example 15.5. Setting the default connection for all models in production

Now you have all models configured to use either the myPostgresqlServer connection or the productionPostgresqlServer connection, depending on Sails’ runtime mode, as illustrated in figure 15.11.

Figure 15.11. The runtime mode dictates which database connection is used at startup.

Warning

Without brushfire/.gitignore, brushfire/config/local.js will overwrite the configuration settings in brushfire/config/production.js. The reason it’s not overwriting it on Heroku is that .gitignore blocks local.js from the remote repository. If you were deploying Brushfire by a method other than a remote Git repository, brushfire/.gitignore wouldn’t block local.js, and thus its configuration would overwrite your production configuration. Therefore, if you’re deploying using a method other than a remote Git repository, you’ll need to make sure brushfire/config/local.js is not also deployed.

Finally, let’s move the productionPostgresqlServer connection you created earlier to brushfire/config/env/production.js so that all your production configuration is in one place. In Sublime, open brushfire/config/connections.js, and move the production-PostgresqlServer connection to brushfire/config/env/production.js like so.

Example 15.6. Adding the productionPostgresqlServer connection to production.js

15.4.2. Configuring auto-migration settings

When we first started developing Brushfire, changes to our database schema could happen ad hoc because we weren’t concerned with preserving the underlying data. In fact, we have migrations within brushfire/config/models.js set to drop the database tables each time we restart Sails. Needless to say, in production, you’re very concerned with preserving your data each time you restart Sails. In production mode, Sails will override all configuration files regarding migrations and automatically set migrations to safe mode to prevent inadvertent deletion of data.

In development mode, allowing database tables to be dropped each time Sails is restarted is compatible with your existing bootstrap file that creates test data each time Sails lifts. For production mode, however, the existing bootstrap file will attempt to create users that potentially exist, causing an error and preventing Sails from starting up. Let’s change the bootstrap file to add test user records only if no users exist. In Sublime, open brushfire/config/bootstrap.js, and add the following code.

Example 15.7. Changing the bootstrap file to check for existing users

You’ll use the find method to look for records in the user model. Because there could be millions of users, you’ll limit the query to the first record found. If a record is found, you’ll pass control back to Sails without adding any test data.

15.4.3. Creating tables in PostgreSQL

Because production mode will set migrations to safe, meaning no schema changes can be made to database tables, you need a mechanism to configure both the initial and ongoing configuration of the remote PostgreSQL database. To accomplish this, you’ll temporarily connect your local version of Brushfire, which runs in development mode, to your remote instance of PostgreSQL on Heroku. That way, when the local Sails version starts, the remote instance of PostgreSQL will be configured with the initial schema attributes for each table. To do this, however, you need to comment out the existing connection in your local version of Sails and add a temporary connection to your remote database. In Sublime, open brushfire/config/local.js, and make a copy of the myPostgresqlServer connection, as shown here.

Example 15.8. Connecting the local Brushfire to the remote PostgreSQL database

Note

We also added comments to the existing local PostgreSQL instance.

Start the Sails server using sails lift. The table schemas will be set up according to the configuration of Brushfire models. It’s important to note that because your local instance of Brushfire has migrations set to drop, you need to exercise extreme caution when using this technique to initialize the remote PostgreSQL instance. Once Sails loads, you can confirm that the tables and test data were created by navigating your browser to localhost:1337 and signing in as sailsinaction with the password abc123. After confirming that the tables are configured properly, head back to brushfire/ config/local.js, and remove the connection to the remote PostgreSQL instance and restore the connection to the local PostgreSQL database, similar to this.

Example 15.9. Restored local Brushfire PostgreSQL database
...
module.exports.connections = {
   myPostgresqlServer: {
   adapter: 'sails-postgresql',
   host: 'localhost',
   database: 'brushfire'
  }

  // myPostgresqlServer: {
  //   adapter: 'sails-postgresql',
  //    url: 'ADD YOUR OWN HEROKU POSTGRESQL URL HERE',
  //    ssl: true
  // }
};
...

Once you add real production data into the database, you should use only one of the many third-party clients to access your remote production database. We currently use a third-party client called Postico.

15.4.4. Runtime vs. build-time process

Now that you’re about to deploy Brushfire into production, it’s necessary to distinguish between runtime process and build-time process. This distinction is important because there are some tasks you want to run each time the Sails server starts. For example, the bootstrap is a runtime process because you use it to execute when the Sails server starts. Some tasks you want performed only when Brushfire is deployed. For example, you want the production Grunt tasks to execute when you deploy and not each time the Sails server starts. Therefore, you need to change the Grunt tasks to execute when Brushfire is deployed instead of every time the Sails server is started.

15.4.5. Setting up Grunt for production

By default in production mode, Sails will perform different Grunt tasks on your static assets. The production Grunt tasks include concatenating asset files as well as minification of some assets. These tasks can take a few minutes, depending on the size of the project. Heroku has a default 60-second boot timeout. This means that Brushfire needs to start within 60 seconds or it will fail.

Note

In development mode, Sails doesn’t wait for the Grunt tasks to be completed before lifting. In production mode, Sails does wait until all Grunt tasks are completed.

Therefore, you want the static asset preparation tasks to happen at build time to avoid any potential timeout issues. In order to run these tasks during build time and not during runtime, you must make a few configuration changes. First, you’ll turn off Grunt when you’re in production mode. Next, you’ll alter the name of the Grunt task that Heroku will use to build your static assets during build time. Finally, you’ll install an alternative set of scripts that incorporates Grunt into Heroku’s build-time process.

To turn off Grunt in production, you need to alter the Sails startup file named app.js. In Sublime, open brushfire/app.js, and make the following changes.

Example 15.10. Preventing Grunt tasks on startup if Sails is in production mode

Note

The changes you make to the brushfire/app.js file won’t be executed locally using sails lift because app.js is executed only when starting Sails via Node app.js. Heroku, however, starts Sails using Node app.js, and thus the file will be executed on the remote instance of Sails.

In production mode, Grunt is set up by default to run a task named prod located in brushfire/tasks/register/prod.js. Instead, you’ll change the name of the existing production Grunt task to the name Heroku will use when building your static assets. In Sublime, open brushfire/tasks/register/prod.js, and rename the registered task to heroku:production, similar to the following listing.

Example 15.11. Renaming the registered Grunt task for Heroku

Heroku will execute this Grunt task when building a Sails dyno. Finally, the scripts Heroku will use to build your Sails dyno are called buildpacks. Default Heroku buildpacks don’t support Grunt, so let’s set the buildpack location to an alternative set of buildpacks that do support Grunt. Head back over to the terminal window and type the following commands on the command line:

~/brushfire $ heroku buildpacks:set https://github.com/mbuchetics/heroku-buildpack-nodejs-grunt.git

Heroku will now execute your Grunt tasks as part of its build-time process.

15.4.6. Deploying to Heroku

You’re now ready to deploy Brushfire to Heroku. Save your changes to the local Git repository by returning to the terminal window and typing the following commands on the command line:

Now that you’ve committed your changes to the local repo, deploying Brushfire to Heroku is as simple as pushing your local repository to the Heroku remote repository. Again, from the command line type

~/brushfire $ git push heroku master

At the end of the build process, Heroku will display the URL of the Brushfire instance. Navigate your browser to the logged URL, which should look similar to figure 15.12.

Figure 15.12. Brushfire deployed and running on Heroku

Although Brushfire has been deployed, you still have some work to do. Chad has high hopes for growth and wants you to plan for Brushfire to use multiple Heroku dynos. Therefore, you need to transition from individual session and WebSocket stores on each Brushfire instance to a central store for all dynos.

15.5. Configuring sessions and sockets for production

During development, you stored session information in memory on your local machine. Now that you have the potential for multiple instances of Brushfire in production, you need to configure a centralized session store for any potential number of Heroku dynos. You’ll use Redis as the database to store sessions. Recall that Redis is an in-memory datastore using key/value pairs similar to a dictionary. Similar to PostgreSQL, Heroku provides remote Redis instances as a DaaS. Let’s set up Redis in Heroku.

15.5.1. Provisioning a remote Redis To Go instance

To add a remotely hosted Redis database on Heroku, navigate your browser to the Heroku dashboard Resources page. You should see the existing remote PostgreSQL instance as one of the add-ons. The process of adding Redis is similar to what you did with PostgreSQL. From the Resources page, click Find More Add-ons. Next, click the Redis To Go service, followed by the Login to Install button, followed by the Install Redis To Go button. Select your Brushfire instance and click Submit. Finally, ensure you’re using the free Nano plan and click the Provision button. Similar to the remote PostgreSQL instance, Heroku creates a configuration variable named REDISTOGO_URL that contains the credentials for the Redis To Go datastore. You’ll use this variable and its value when configuring sessions and WebSockets.

15.5.2. Configuring a remote session store

Sessions are currently stored in memory in each instance of Sails. Now that you have a remote Redis database, you can configure each Sails dyno to use this single remote instance. In Sublime, open brushfire/config/env/production.js, and add the following to enable the Sails session store to Redis.

Example 15.12. Configuring Redis To Go for sessions in production

Now that you have the connection configured, let’s install the adapter. From the terminal window, type

~/brushfire $ npm install connect-redis --save

Once it’s installed, sessions will now be configured to use the Redis To Go instance on Heroku for Brushfire in production mode.

15.5.3. Using Redis to deliver notifications

Similar to sessions, WebSockets are currently stored in memory for each instance of Sails. You need to configure each Sails dyno to use this remote instance. In Sublime, open brushfire/config/env/production.js, and add the following code to enable the Sails WebSocket store to Redis.

Example 15.13. Configuring Redis To Go for WebSockets in production

Now that you have the connection configured, you need to install the adapter. From the terminal window, type

~/brushfire $ npm install socket.io-redis --save

Once it’s installed, WebSockets will be configured to use the Redis To Go instance on Heroku for Brushfire in production mode.

15.5.4. Using Redis in development (so you don’t have to log in all the time)

There’s no real performance or scalability advantage to using Redis for WebSockets in development. But there is a benefit to using Redis with sessions. Sessions are stored in memory on your local machine. Therefore, any time you restart Sails, the sessions are lost because they’re stored in memory on the same machine. Installing a local instance of Redis and configuring Sails to use it during development eliminates the need to sign in after restarting the Sails server. In Sublime, open brushfire/config/session.js, and uncomment the adapter, similar to the following listing.

Example 15.14. Configuring a local Redis database for sessions in development

Information about installation can be found at http://redis.io/download. For OS X, we used the package manager Homebrew. Once installed, the Redis server can be started by typing the following from the command line:

~/ $ redis-server

Sails will now use the local Redis instance in development mode and the remote instance in production mode. To confirm that Redis is running locally, restart Sails using sails lift. Navigate your browser to localhost:1337 and sign in to Brushfire using sailsinaction and the password abc123. Now, restart the Sails server using sails lift. If you go back to your browser and refresh, you should be logged in. This is because the session database is now separate from your Sails instance.

15.5.5. Configuring Mailgun for email delivery

You need a way to configure the Mailgun service credentials you implemented earlier in chapter 11. You can configure Heroku to set environment variables, much like you configured your PostgreSQL database credentials by storing them securely on Heroku. Add the environment variables by navigating your browser to the Heroku dashboard for your Brushfire instance and clicking Settings. Then, from the Settings page, click Reveal Config Vars. After you add the environment variables, your browser should look similar to figure 15.13.

Figure 15.13. The Mailgun credentials include the API key , the base URL , and the domain .

Next, you’ll add these environment variables to the production configuration file. In Sublime, open brushfire/config/env/production.js, and add the following Mailgun settings.

Example 15.15. Configuring Mailgun credentials for production

In production, Brushfire is now properly configured for Mailgun. Commit the changes from the terminal window by typing

~/brushfire $ git commit –am 'added mailgun credentials'

And then push the results to Heroku by typing

~/brushfire $ git push heroku master

Let’s move on to a very important topic: testing.

15.6. Testing

The amount of testing needed for a project can be a highly contentious subject. In this section, we’ll show you how to implement and configure a testing environment that supports many different testing styles. When planning for this book, we considered using test-driven development (TDD) and realized that including that discussion could double the length of the manuscript. In the end, we compromised on providing this discussion that allows you to implement your own style of testing.

You can use many libraries and frameworks to test Sails, but in this book you’ll use a JavaScript testing framework named Mocha. Mocha allows you to create test cases, execute those cases, and receive the results via reports. You’ll also use SuperTest, another testing library, which allows you to easily create test cases that make requests to your routes and controller/actions. SuperTest also provides a way to make requests using existing sessions.

15.6.1. Installing dependencies for your test suite

In this section, you’ll install Mocha and SuperTest as well as configure the folder structure that will contain your tests. From the command line, install Mocha by typing

~/brushfire $ npm install mocha --save-dev

By using the –dev tag, you’ll install Mocha as a development dependency, and, therefore, it won’t be installed when Brushfire is deployed in production mode. You’ll do something similar for SuperTest. From the command line, type

~/brushfire $ npm install supertest --save-dev

Now that both frameworks are installed, you can create a folder structure for your tests. From the root of the Brushfire project, create a folder named brushfire/test/. You’ll add all your tests, helper functions, and fixtures to this folder.

15.6.2. Using before() and after()

Mocha provides functions where you can set up preconditions before tests as well as clean up after tests. In Sublime, create a new file named brushfire/test/bootstrap.test.js, and add the following code.

Example 15.16. Adding before and after hooks to Mocha

This Mocha bootstrap file is very useful for running tasks before and after a test and should not be confused with brushfire/config/bootstrap.js, which is executed when the Sails server starts. Next, let’s see how you can use npm to start your tests from the command line.

15.6.3. Running tests from the command line

You’ll use npm from the command line to initiate tests. To set this up, open brushfire/package.json in Sublime, and add the following to the scripts dictionary.

Example 15.17. Configuring npm to initiate tests from the command line

Let’s see this in action. From the command line, start Mocha by typing

~/brushfire $ npm test

Because you don’t have any tests yet, you should see something like 0 passing (1ms) from the command line. This will not only initiate Mocha tests but also set the runtime mode of Sails to test. Before you start testing, you’ll set up a test environment mode file similar to what you did for production and development modes.

15.6.4. Configuring your test environment

You currently have configuration files for development and production modes. Let’s add one for your test environment. In Sublime, create a file named brushfire/config/ env/test.js, and add the following code.

Example 15.18. Configuring npm to initiate tests from the command line

It sometimes works well to use the sails-memory adapter to speed up testing. So, during testing, you’ll configure models and sessions to use the sails-memory adapter instead of the PostgreSQL and Redis adapters. To install the sails-memory adapter, head over to the terminal window, and type

~/brushfire $ npm install sails-memory --save-dev

You’re now ready to add your first test case.

15.6.5. Understanding tests

Before diving into an actual test of Brushfire, we thought it would be easier to start our discussion with a simple test case that isolates the structure of a test. In the brushfire/test/ folder, create a subfolder named integration/. This is where you’ll aggregate your tests. Create the test in Sublime by creating a file named brushfire/test/ integration/test.js and adding the following code.

Example 15.19. Fundamentals of a test

The assert library is a core module in Node. One of the purposes of the library is to provide methods that you can use to test whether something is true. The describe() method is a way to group multiple tests under a particular namespace often referred to as a test suite. You can also nest the describe() method in another describe() to create subgroups. Observing the results of a test will provide greater clarity on how and why you use these methods. The it() method creates an actual test case. Anything that throws an error within the it() method will cause the test to fail. Execute your first test by heading to the terminal window and typing the following from the command line:

~/brushfire $ npm test

The result of the test should look similar to the following.

Example 15.20. The results of executing test.js

Looking at the log results of the test, you can see that your first test passed.

15.6.6. Testing an endpoint

Now that you have an initial test under your belt, let’s create a test for a portion of your signup-a-user endpoint. The route consists of a POST request to /signup that triggers the signup action of the user controller. There’s a policy on this action that requires a user to be logged out and therefore not authenticated in order to access the action. In order to test whether this policy is working, you need to create and authenticate a user as part of the setup for the test. In Sublime, create a new file name brushfire/test/integration/create-user.js, and add the following code.

Example 15.21. Creating the user before the test

Again, you use the before() method. But this time you use it within a describe() method. Therefore, instead of the before code executing prior to all tests, as it did in bootstrap.test.js, this code will execute before the tests within this particular describe().

Note

We’ve made this before() method asynchronous by adding done as an argument in the callback. Thus, no tests will be run until before() returns control.

Next, you’ll configure SuperTest’s agent dictionary, which allows you to make requests with persisted cookies.

Note

There are many ways to make client requests of a server. You can rely on a browser to make a request, or you can rely on a library that uses methods that issue requests on your behalf. The SuperTest agent allows you to simulate a browser request, including things like cookies used with sessions.

You encrypt the test user password and then create the user before making a request with your agent dictionary. The request authenticates the test user and then passes control back to Mocha to execute the remainder of the tests.

Now that you have an authenticated test user, let’s use it to test the policy. Head back to Sublime, and add the following code to brushfire/test/integration/create-user.js.

Example 15.22. Testing the signup-a-user endpoint

First, you create an asynchronous test case by adding done as an argument to the callback of the it() method. Then, you make a request that will trigger an attempt to create a user using the agent method.

Note

The policy should prevent an authenticated user from reaching the signup action and produce the 403 status code.

Finally, you confirm that the returned status code is 403 by using the assert() method. Let’s see this in action. From the terminal window, type the following on the command line:

~/brushfire $ npm test

You should now have two passing tests.

15.6.7. Refactoring a test using fixtures and helper functions

At times, you’ll want to refactor some repeated aspects of a test into a reusable component. For example, let’s refactor the test-user properties into their own fixture. Within brushfire/test/, create a subfolder named fixtures. In Sublime, create a new file named brushfire/test/fixtures/user.js similar to the following listing.

Example 15.23. Refactoring the test user data into fixtures
module.exports = {
  username: 'testtest',
  password: 'abc123',
  email: '[email protected]'
};

You can now require this fixture any time you need it. Next, let’s refactor the creation and authentication of a test user into its own utility method. In Sublime, open brushfire/test/integration/create-user.js, and replace the bulk of creating and authenticating a user with a new method appropriately named create-Test-User-And-Authenticate(agent, done) similar to the following.

Example 15.24. Introducing a new helper function in a test

Next, let’s implement the createTestUserAndAuthenticate() method. Within brushfire/test/, create a subfolder named utils. You’ll use the utils subfolder as a place for your utility test methods. In Sublime, create a new file named brushfire/test/utils/ create-logged-in-user.js, and add the following code.

Example 15.25. Implementing the createTestUserAndAuthenticate method

Let’s again see this in action. From the terminal window, type the following on the command line:

~/brushfire $ npm test

After the refactor, you should still have two passing tests, except you now have a utility function you can use throughout your test suites. Next, let’s do some validation testing on the user model.

15.6.8. Testing model methods and validations

The POST /user/signup endpoint has several initial validations. Let’s test one of the validations: when a user hasn’t supplied an email address. In Sublime, open brushfire/test/integration/create-user.js, and add the following code.

Example 15.26. Testing the missing email validation

These tests will require a new user that is unauthenticated, so you don’t need any test user creation and authentication setup. The validation requires an email address, so you’ll make a request to sign up a new user without an email address. The test is expecting a 400 Bad Request status code. Give it a try. Start the test from the terminal window via npm test. You should now have three passing tests. Finally, create a test for a successful signup. In Sublime, open brushfire/test/integration/create-user.js, and add the following test.

Example 15.27. Testing for a successful signup

Here, you’re testing whether the user was created successfully in various ways:

  • Did you receive a 200 status response?
  • Did you receive a valid username?
  • Did you receive a valid gravatarURL?

Once again, let’s execute the test via npm test. You should now have six passing tests. With these testing components, you have the tools necessary to test all aspects of your controller/actions.

15.7. Security

Although we’ve been working on Brushfire security concepts throughout the book, we wanted to extend our discussion with a section that identifies an application’s core security vulnerabilities as well as the various steps you can take to protect against them. Figure 15.14 focuses on the top vulnerabilities of each layer of Brushfire’s technical stack.

Figure 15.14. An overview view of the security vulnerabilities of Brushfire’s technical stack includes attacks related to the frontend, attacks related to the network, and attacks related to the backend.

First, we’ll address frontend vulnerabilities due to cross-site scripting (XSS) and cross-site request forgery (CSRF) attacks , as well as preventive measures to protect against them. Next, we’ll look at how to protect against man-in-the-middle (MITM) network attacks . We’ll end with addressing backend vulnerabilities and protections . We encourage you to explore the Sails security site at http://sailsjs.org/documentation/concepts/security as well as the Open Web Application Security Project (OWASP) site at http://mng.bz/yXd3. Each destination provides a wealth of timely information pertaining to many aspects of application security.

15.7.1. Frontend, network, and backend security

Before addressing specific vulnerabilities, it’s important to discuss your control or lack thereof of each layer. Relative control is directly tied to a layer’s exposure to changes without your consent. For example, on the client side, you’ve provided validation logic through Angular that prevents the creation of a user that contains characters other than spaces (the letters Aa–Zz, or the numbers 0–9). But anyone can circumvent those validations because you have no control over changes to the browser user-agent. You have to design your security around an assumption that anything you do on the browser user-agent is subject to change and, therefore, any data from the frontend layer is untrusted and possibly malicious. In the validation example, because frontend validations can be thwarted, you must also create the same validations on the backend.

The network layer is also outside your control and subject to changes. Again, you have to protect against the possibility that someone could intercept and change your requests/responses before they reach the user-agent or server. Finally, unlike the browser user-agent or network, you can reasonably expect that what you create on the backend won’t change without your consent. That doesn’t mean you can’t create unwanted vulnerabilities. It just means that whatever you’ve baked in won’t be subject to change.

15.7.2. Understanding cross-site scripting attacks

Cross-site scripting (XSS) attacks occur when an attacker injects malicious scripts into an otherwise benign web page, causing the browser to do things counter to the best interest of the user. These malicious scripts can be injected in a variety of ways. The most prevalent way is a stored or persistent attack.

Tip

Once stored, a persistent attack can occur any time a user renders the injected page.

A persistent attack occurs when the malicious script is stored in a database. It’s then executed when the stored data is injected and rendered on a page.

15.7.3. Protecting against XSS attacks

The best defense against an XSS attack is to HTML escape any untrusted data that’s inserted into a page.

Definition

HTML escaping is a process of replacing the characters of potentially harmful scripts with an HTML entity. For example, you don’t want the <script> tag to be executed in any data you inject in the page. HTML escaping replaces the less-than (<) sign with the HTML entity &lt; and the greater-than (>) sign with the HTML entity &gt;. Therefore, the <script> tag becomes &lt;script&gt; which renders as <script> but does not execute as <script>.

Figure 15.15 illustrates HTML escaping of potentially malicious user-provided data.

Figure 15.15. After HTML escaping, the malicious script is rendered harmless before it’s injected into a page.

Brushfire uses EJS template tags, <%= %>, and Angular template tags, {{ }}, to HTML escape data on a page. For example, these tags automatically escape characters like the less-than (<) and greater-than (>). A majority of the time, you’ll use the EJS <%= %> template tag that HTML escapes content. There are two instances, however, where you want to inject data into a page without HTML escaping. In brushfire/views/layout.ejs, you use <%- body %> and <%- partial() %> to inject other views into the layout. These tags don’t do HTML escaping and instead pass the responsibility of HTML escaping untrusted data to the views they inject. You also use the EJS <%- %> template tag when bootstrapping data on a page. Any time you inject user content without HTML escaping, you open yourself to risk, so it’s important that you can trust whatever you’re injecting without HTML escaping it first.

Note

As of the publish date of this book, Sails v1.0 hasn’t been released. But when it is, you’ll be able to take advantage of an easier approach for injecting data on the page using script tags. Take a look at the Sails v1.0 release notes at http://sailsjs.org/support for more information.

15.7.4. Understanding cross-site request forgery attacks

A typical cross-site request forgery (CSRF) attack occurs when a user who has authenticated to a trusted site and has a valid session unwittingly executes a script that uses the existing browser session to perform a malicious act as an authenticated user, as shown in figure 15.16.

Figure 15.16. A typical CSRF attack begins with a user logging in to a trusted site. The user clicks a malicious link that appears to be legitimate but is actually a page on an untrusted site that uses the existing session to do bad things.

For example, a user navigates their browser to a site they trust and then logs in. The login process establishes a session between the browser user-agent and the trusted website. Any subsequent request made from the browser user-agent while the session is active, regardless of whether the request is made from a new window or tab, will appear from the perspective of the server to be from that authenticated user. An attacker could, for example, use the hijacked session for a malicious act that changes the user’s password. This type of attack does require that the application have an XSS vulnerability or that the user click a malicious link from some other source such as a link in an email that triggers a request from the authenticated browser.

Sails provides protection against this style of attack using a CSRF token. Whereas a session tells the server that a user is who they say they are, a CSRF token tells the server that a user is where they say they are. When enabled, all non-GET requests to the Sails server must be accompanied by a special token, identified as the csrf parameter, similar to figure 15.17.

Figure 15.17. With CSRF protection enabled, a user logs in to a trusted site. The user clicks a malicious link that appears to be legitimate but is actually a page on an untrusted site that uses the existing session to attempt to do bad things. Without the csrf token, a malicious request will be rejected by goodGuys.com.

This time-stamped secret CSRF token is generated by the server and made available via either a local variable named csrf in a view or via a GET request to the /csrfToken route, where the token will be returned as JSON. Without this token, the would-be attacker will be prevented from making non-GET requests. Let’s enable CSRF protection in Brushfire and then look at the various ways of obtaining and including it in non-GET requests.

15.7.5. Enabling CSRF token protection

Enabling CSRF token protection is simple. In Sublime, open brushfire/config/csrf.js, and change the csrf property value to true, similar to the next listing.

Example 15.28. Enabling csrf token protection
module.exports.csrf = true;

The Brushfire backend will now expect a csrf token in every non-GET request. If no token is provided, the request will fail.

15.7.6. Sending the CSRF token

Now that you’ve enabled the use of CSRF tokens, you need to make sure the token is passed into all non-GET requests. You can either add the token to each request manually or utilize an automated method to add a header to all HTTP and Web-Sockets requests.

Note

HTTP and WebSockets use headers to communicate configuration properties of requests and responses.

Angular provides a way to set common headers in HTTP requests for a given application. In Sublime, open brushfire/assets/js/app.js, and add the following code.

Example 15.29. Adding a common csrf token header to Angular HTTP requests

Now all HTTP requests will contain a header with the csrf property to Sails. Next, you need to bootstrap the csrf token to the window dictionary using the csrf local variable Sails provides you from a view. In Sublime, open brushfire/views/layout.ejs, and add the following code.

Example 15.30. Establishing the csrf token as a property on the window dictionary

Because you add sails.io.js manually outside the Grunt SCRIPTS tag, you need to remove sails.io.js from the Grunt pipeline. In Sublime, open brushfire/tasks/pipeline.js, and comment out the sails.io.js dependency, similar to this.

Example 15.31. Removing sails.io.js from the pipeline.js file

This takes care of non-GET HTTP requests, but you also need to bootstrap the csrf token for all WebSocket requests. In Sublime, open brushfire/views/layout.ejs, and add the following code near the bottom of layout.ejs.

Example 15.32. Adding the csrf token as a common header for WebSocket requests

All of Brushfire’s non-GET requests use either Angular or WebSockets. But there may be times when you want to access the csrf token from some other AJAX request via a GET request to /csrfToken. For example, the next listing illustrates how to incorporate the token request as part of a series of AJAX requests.

Example 15.33. Inserting a csrf token via a hidden input field

Although we don’t recommend using HTML form requests in your application, the following listing illustrates how to configure them for CSRF tokens.

Example 15.34. Inserting a csrf token via a hidden input field
<form>
  <input type="hidden" name="_csrf" value="<%= _csrf %>" />
</form>

When the form is submitted, the csrf token is also submitted, allowing the request and form fields to be passed through to the controller action.

15.7.7. Disabling CSRF protection in tests

You need to disable CSRF tokens in your test environment because your goal is not to test the framework but instead to test your application. In Sublime, open brushfire/config/env/test.js, and add the following property.

Example 15.35. Disabling csrf tokens in the test environment

Let’s commit your changes and push them to Heroku. From the terminal window, type

15.7.8. Understanding cross-origin resource sharing

A page obtained from a server and rendered by a browser user-agent that contains a script can’t successfully make a request using that script to a server from a different domain. This is because the request violates the browser’s same-origin policy.

Definition

A browser’s same-origin policy restricts access of scripts, specifically AJAX, from accessing data on a different web server than what originally delivered the script.

This prevents malicious script attacks across domains.

Note

If the malicious link triggers a form submission request as opposed to an AJAX request, the browser’s same-origin policy doesn't restrict the form request. Because a form request isn’t subject to the same-origin policy, the request is allowed. But the form request can’t receive a JSON response like an AJAX request, so the endpoint is protected.

There are very limited occasions, however, when you might need to divide your application across multiple domains and use cross-origin resource sharing (CORS) to open cross-domain access. Sails can be configured to allow cross-origin requests from a list of domains you specify or from every domain. This can be done on a per-route basis or globally for every route in your app. Unless you completely trust the domain, we highly recommend against using CORS. And if you use CORS, you should limit the domains that can access it. CORS is beyond the scope of the book, but additional details about configuring it can be found at http://mng.bz/555D.

15.7.9. Understanding man-in-the-middle attacks

Man-in-the-middle attacks occur because of your inherent lack of control over the network. You can’t be certain that a request traveling over the network between the front-end and the backend hasn’t been intercepted and altered. Protecting against MITM attacks is straightforward. You need to encrypt the data that passes between your requests and responses. To do this, you need transport layer security (TLS), which is beyond the scope of the book. But Brushfire is currently piggybacking on top of Heroku’s TLS via https://[email protected].

15.7.10. Denial of service attacks

When you think about a denial of service (DoS) attack, you’re most likely thinking about thousands of nodes making requests to a particular endpoint, interrupting service of the application. In reality, a DoS attack is any attempt to interrupt service of an application. Ironically, many times the interruption of service isn’t intentional. At the application level, you can write things in your code that make you vulnerable to DoS attacks. As we said, in many cases these are accidental DoS attacks!

For example, if you write code that doesn’t use .limit() in your queries, you’re going to get all of the records. So, for the first few months of your application, there are no problems. But as your application grows, and you have a few hundred thousand records or a few million, every time a request is sent to that controller action, it will load all the records. Once again, that might be okay for a few requests, but as those requests build, you’re now loading 20 or 30 million records. This can quickly take down your application due to running out of RAM and then disk, and finally you have a pile of hot liquid metal. The way to work around this is to always use .limit() in find, update, and destroy methods that use criteria that could generate an unlimited number of records.

Another possible attack can occur by not limiting the number of things that can be created. For example, a tutorial can currently have an unlimited number of videos. But let’s put an arbitrary limit of 25 videos per tutorial. The limit would need to be set in the newVideo action of the page controller as well as the addVideo action of the tutorial controller. The following listing depicts some initial code to set a limit for the newVideo action.

Example 15.36. Limiting the number of videos in a tutorial

The final type of unintentional attack is failure to wrap synchronous functions in a try/catch, which can cause the server to crash. Remember to not only wrap the synchronous function with try/catch but also to handle the underlying error. The next listing illustrates the correct use of try/catch with a synchronous function.

Example 15.37. Wrapping synchronous functions in try/catch
     ...
   try {
     var randomString = Strings.unique({}).execSync();
   } catch (err) {
     return res.serverError(err);
   }
...

15.7.11. SQL injection attacks

There are times when you may need to go beyond the features of the ORM. Sails and Waterline don’t restrict you from accessing the database directly using the .query() method. But you do have much more responsibility in protecting the way user data interacts with the query and the database directly. Waterline provides these types of protections if you’re using model methods like find, create, update, and destroy. But you have the responsibility when using .query(). For more information on using .query(), see http://mng.bz/N4z3.

15.8. Summary

  • You now understand containerization and horizontal/vertical scaling of an application.
  • Sails uses development, production, and test environments that enable different configurations of Sails, depending on the environmental mode.
  • Environment variables allow you to control Sails’ startup configuration as well as security transmit credentials within Sails when it’s deployed.
  • Sails can use Mocha and SuperTest to test controller actions and models.
  • Sails uses tokens to protect requests against CSRF attacks.
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset