Skip to content
This repository has been archived by the owner on Apr 8, 2019. It is now read-only.

outmoded/university

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

74 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

hapijs university

Use JavaScript and hapijs to build web applications. See the essentials of how to build hapi applications: authentication, validation, application architecture, and testing. Plus, delve into deeper topics like: bearer-tokens, caching, the request lifecycle.

Assignments and Solutions

[Assignment1] Let's get started!

First, we need some basic structure: a lib folder, a package.json file, and an initial ./lib/index.js. Since we will be using hapi, we need to include that in the project dependencies. Second, create a basic hapi server on port 8000 which responds to /version requests and replies with a simple { "version": "0.1.1" } JSON payload. The version data in the response should come from the package.json file.

npm init
npm install --save hapi

View Solution

credits for assignment1

Above assignment is from original assignment1 by @hueniverse and work by @AdriVanHoudt. See this PR.

pre-assignment2 reading:

[Assignment2] plugins

The right way to work with hapi is to build your application using plugins. Plugins provide a way to organize your code into logical components and then put them together in different combinations or deploy them in different configurations. While some plugins are published as general purpose utilities (e.g. adding authentication), you should think of plugins as a way to break your code into pieces.

Now that we have a basic server with one endpoint, we want to move the creation of the endpoint to a plugin, and then register that plugin with the server. Create a new file lib/version.js and move the /version endpoint there. Then, change our current lib/index.js to require the new plugin file and register the version plugin with our server. The server.register() function is used to register the plugin.

Remember to follow the style guide, and ask any questions in the comments of the issue created for the assignment. If you are not sure how to get your fork back in sync with the current updated code, use the git guide.

/version end point should reply with a simple JSON payload:

{
    version: Package.version,
    message: options.message
}

Highlights

  • Notice that const plugins = [] is an array. An array is used to register the Version plugin because more plugins are to be registered in later assignments.
  • Notice how options are passed to the plugin: { message: 'assignment2' }. The /version point returns the message value.
  • In contrast to plugin options, notice how server.register options are passed: { once: true }. Often there is confusion between hapi's server.register options and options passed to a plugin being registered. lesson2 clearly shows the difference. { message: 'assingment2' } illustrates options passed to a plugin. These are options used inside the Version plugin. And, the { once: true } option is passed to the server when registering plugins: server.register(). See /lib/index.js for details.

Helps

  • require('./version') should be declared at the top and assigned to Version`.
  • no need for specifying the plugin version in the attributes. This is an internal plugin, not a publicly published one.

compare assignment2 solution to assignment1
view assignment2 solution source

Assignment2 Credits

This assignment is from the original assignment2 convert to plugin and discussion related to the assignment written by @hueniverse. The code solution is from work done by @MylesBorins.

[Assignment3] Testing & 100% coverage

Things are getting a bit more interesting...

It's time to add tests, verify coverage, confirm style, and automate all of this with CI (CI means: Continuous Integration). We will be using the lab module to perform these tasks and automate them with travis. Code will be our test's assertian library.

  1. Export init() and move the server invocation to a new lib/start.js file. The lib/start.js file calls the exported init() function and passes configurations options to it. The resolved promise function in start.js outputs the server config details to the console. Change package.json file to use start.js as the start up script. start.js file is not covered by tests. Designing the server to start with an exported init function allows other scripts and applications to start and stop the server. This is important for several reasons:
    • It allows testing scripts to start and stop the server to execute tests.
    • It allows another program to start and stop the application. This is useful when using hapi to build services - soa (service oriented architecture). Sometimes you make several hapi services to run on one computer and collaborate with each other.
    • It allows for the start.js script to start and stop the application.
  2. Add a .travis.yml file. When a .travis.yml file exists in a GitHub repository the project is built and all tests are executed. .travis reports if all tests successfully pass or not. Note, you must configure github to excute travis CI upon events (push or PR) to the repository. This is found under: Settings -> Integration & Services.
  3. Add a test folder with two files, version.js and index.js, each testing the corresponding file under /lib.
  4. Modify the package.json file to include tests. Install the dev dependencies lab and code.
  5. When using lab, enable coverage, require 100% coverage, enable linting with default rules, and install and use code assertion library. See package.json file to view the test command or see a test command write up here.
  6. Write a basic test to verify our version endpoint in version.js.
  7. Write tests to get 100% coverage. To get 100% test coverage you also need to confirm style. lab confirms if the project's code abides by the hapijs style guide. This is called 'linting'.

Everything should be pretty straight forward. If you are not sure on how to use lab and code, look at other hapi.js modules and copy their test scripts and setup.

Getting 100% coverage can be tricky sometimes so if you are not sure, get as much coverage as you can, and comment on the lines in your pull request where you are having a hard time reaching and someone will give you a clue.

Remember to properly stop() your servers when calling the init() method in each test.

For now, avoid using any of the before() and after() lab features.

As always, ask for help and help others!

Helps

  • When stubbing / patching code in a test, mark that test with the { parallel: false } lab option to make it both safe for future parallel testing as well as visual cue.
  • Never return anything other than an actual Error as an error (e.g. no strings, plain objects, etc.).
  • Never use fixed port numbers in tests, only 0 or the default.
  • Always return before next() unless there is a reason to continue.
  • Make arguments / options in init() required.
  • When calling server.inject() with a GET request, just pass the path string as the first argument instead of an options object. Makes the code much more readable.
  • Use the testing shortcuts boilerplate used in hapi. Makes reading tests easier.

lab summary

  • elegant lab and code
    good lesson three game plan
    go test for dirty bugs
    • clean up guy on github see travis agree
    • talk style, value guidance, hapi emotion,
      lab enforces all.
      Seek linting, Geek leadership no excuses find lab have fun.

Compare Assignment3 Solution to Assignment2
view assignment3 solution source

Credits

Assignment is from original assignment3 and discussion related to it. The author was @hueniverse. Original code for the solution was written by idanwe. See: PR for original source code for solution. The .travis.yml file is from the hapi project.

[Assignment4] Use server.app properties

  • Read documentation server.app property
  • Use the server.app property to store the application's response.version and options.message.
  • Access the request.server property in the ./lib/version.js handler to return the server.app.version and server.app.message values.
  • Note: The server.app property is useful to set a DB connections in. server.app properties are available wherever the request.server is exposed. As the documentation says:

    server.app provides a place to store run-time application data. Data can be accessible wherever the server object can be accessed.

Compare Assignment4 Solution to Assignment3

[Assignment5] Project Structure

  • In this lesson we change the /version route handler function location. The goal is design program code that is easy to maintain and reuse. Moving route functions from the plugin which registers routes makes for readable route registration plugins. Plus, it allows for easier maintenance and reuse of methods or functions.
    • When a plugin registers multiple routes or has routes with the request-lifecycle extended, plugin code can get cluttered. Moving method / function logic out of the plugin keeps route logic readable. Lesson8 will explore the request-lifecycle more.
    • There is a limitation to removing route functions from the plugin registering the route. Options passed to the plugin at registration time are not available to functions built in files seperate from the plugin. However, usually this is not an issue. Or, the issue can be by-passed using server.app properties.
  • Create a route methods directory ./lib/route-methods. Methods used in routes will be stored in this directory.
  • Create a version.js file in the route-methods directory ./lib/route-methods/version.js
    Methods used in the /version route will be stored here.
    Move the /version handler function to this file.
  • Note: this is a personal style preferrence.
    Preferrence is to make a split screen view with:
    • the screen on the left
      displaying routes ./lib/version.js
    • the screen on the right
      displaying methods executed in the routes ./lib/route-methods/version.js
  • Run tests. No new tests need to be built. But, need to increment lesson value to 1.0.5. Executing passing tests proves changes did not break the application. Enjoy the benefits of CI Continuous Integration.

Compare Assignment5 Solution to Assignment4

[Assignment6] auth bearer tokens

  • add hapi-auth-bearer-token
    npm install --save hapi-auth-bearer-token
  • Register the authentication strategy in it's own plugin ./lib/authtoken.js.
  • all routes must have valid token to be accessed
    • currently only one route exists: /version.
    • valid token equals 1234574
  • 100% tests coverage.
    Adjust /version tests for token authentication. Test for passing and failing tokens.

Notice we have not created user authentication yet -- users have no way to log in. Tests for the assignment assume a valid auth bearer token for the user already exists. The focus is on getting hapi auth bearer token plugin installed and configured. This lesson does not build a complete authentication system.

Here are resources related to auth bearer tokens. Please share if you know of other links and resources related to the subject.

Compare Assignment6 Solution to Assignment5

Notes on original authentication assignment

This assignment started as assignment4.
It contains good discussion regarding authentication issues. For the original solution see: PR. It used hapi-auth-basic.

[Assignment7] TLS

Credits

Original TLS assignment completed by @rutaihwa.

Compare Assignment7 Solution to Assignment6

[Assignment8] /authenticate route

  • build ./authenticate route.
    This moves us one step closer to completing the authentication system.

  • Build data store database.js to authenticate user records with. User records contain scope values to implement hapi's way of doing RBAC (role based access control). For this project their are two scopes: ['admin', 'member']. admin is administrative user and member is a normal user.

  • methods executed in the /authenticate route are stored in the ./route-methods/authenticate.js file. This seprates logic:

    • ./lib/version.js contains route registration logic.
    • ./route-methods/authenticate.js contains methods executed in the route.
    • The /authenticate route utilizes hapi's prerequisite request-lifecycle extension. The pre method is executed before the handler.
  • Request-lifecycle extensions allows for logic to be split up into multiple functions and be executed at specific times when a request is made to a route.
    In respect to route.options.pre methods,

    These methods allow breaking the handler logic into smaller, reusable components that can be shared across routes, as well as provide a cleaner error handling of prerequisite operations (e.g. load required reference data from a database). Source: route.options.pre

    See other documentation for more about the request lifecycle:

    The request lifecycle is an essential part of the hapi framework. As the documentation says: "Every incoming request passes through the request lifecycle". Therefore, you want to be familiar with it.

  • No authStrategy applied to /authenticate point.

  • generate bearer-token upon successful authentication (cryptiles).

  • Use Boom to return errors.

Compare Assignment8 Solution to Assignment7

Credits

  • Lesson has some influence from assignment4 of earlier version.

[Assignment9] tokens, cache, basic authentication system

This lesson completes a basic token based authentication system. Currently, our server only has two routes: /version and /authenticate. Only users with authentic bearer tokens can access server routes. See token strategy in: ./lib/authtoken.js. The lib/authtoken.js strategy is crude supporting one static token. On the /authenticate route we turn off the requirement for an authentic bearer token with the false option because unauthenticated users do not have bearer tokens yet. The false option exposes routes to public unauthenticated users.

At this point, there is a disconnect in the system. A user can generate a valid auth token on the /authenticate route. But, that token is not stored for future use. As stated earlier, the authentication strategy only considers one token as valid (1234574). To resolve this issue we use redisdb and hapi's caching plugins.

First, configure a bearer token cache. When a user successfully authenticates, the auth-bearer-token generated for the session is stored in the cache catabox-redis. Along with the token, the authenticated user account data is also stored in the cache. This is where a users scopes are stored. Scope values determine which routes a user can access. Second, the validateFunction for the auth-bearer-token strategy is modified to use the bearer token cache to validate received tokens. This solves the above mentioned disconnect in our bearer token system. Third, we create a /private route which requires administrative user privileges (admin scope) to access route data.

  • auth-bearer-token cache
    • install redisdb
    • configure server to use catbox-redis. This is done in the ./lib/tokencache.js plugin.
      See hapi caching and catbox-redis documentation.
    • Upon successfull authentication set the bearer-token in cache along with the user record: ./lib/route-methods/authenticate.js.
    • Expire the token after xxxxx time. Set expiresIn: value with server.options. This allows tests to configure shorter expiration times to avoid sessions created from previous tests colliding.
    • configure scopes ['admin', 'member'] for role based access.
    • configure .travis.yml to use the redis server
  • authentication
    Refactor authentication logic to:
    • pre-empt one user from generating multiple tokens using an active cache. active cache is configured in ./lib/tokencache.js.
    • upon successful authentication set token and user record information in the authtokens cache. authtokens cache configured in ./lib/tokencache.js.
    • The ./lib/route-methods/authenticate.js logic is where new auth-tokens are stored in the cached upon successful authentication. Plus, logic to pre-empt the creation of duplicate tokens is written here too.
  • lib/authtoken.js
    • re-write the defaultValidateFunc to use the catbox-redis cache to authenticate tokens.
  • server configuration options
    Configure the server application Configs to set the tokencache.js plugin options. This allows for test configurations to be modified. Our test configurations modify token expiration times so tokens from different tests do not collide. See: ./lib/start.js const Configs.plugins.tokenCache.expiresIn.
  • create ./private point which requires admin scope for access
    • Apply default authStrategy to ./private point.
  • tests
    • write 100% test coverage
    • Note FYI (For your information), when building tests for hapi-auth-bearer-token may need to add { debug: false } to your server configs. This avoids hapi-auth-bearer-token printing verbose error reports.
  • See relevant documentation below:

Credits

.travis.yml implementing redisdb is from: catbox-redis project.

Lesson9 solution