Are you Hapi(.js)? (Part 2/2)

László Harri Németh
The Coding Hype
Published in
11 min readMar 26, 2019

This article is the second part of a two-part series about Hapi.js. The first part can be read here. I suggest you to read the first part first.

Keywords: Node.js, NPM, Javascript, Hapi.js, HTTP, Inert, Path, Fs, Rot13-transform, Joi, Hapi-auth-basic, Routing, Static files, Streams, Validation, Upload, Cookies, Authorization

DISCLAIMER: THE VIEWS AND OPINIONS EXPRESSED IN THIS ARTICLE ARE THOSE OF THE AUTHOR AND DO NOT REFLECT THE OFFICIAL POLICY OR POSITION OF THE EMPLOYER OF THE AUTHOR. THE ARTICLE IS NOT ENDORSED BY, DIRECTLY AFFILIATED WITH, MAINTAINED, AUTHORIZED, OR SPONSORED BY ANY CORPORATION OR ORGANIZATION. THE INFORMATION CONTAINED ON THIS ARTICLE IS INTENDED SOLELY TO PROVIDE GENERAL GUIDANCE ON MATTERS OF INTEREST FOR THE PERSONAL USE OF THE READER, WHO ACCEPTS FULL RESPONSIBILITY FOR ITS USE. ALTHOUGH THE AUTHOR HAS MADE EVERY EFFORT TO ENSURE THAT THE INFORMATION IN THIS ARTICLE WAS CORRECT AT THE TIME OF THE WRITING, THE AUTHOR DOES NOT ASSUME AND HEREBY DISCLAIM ANY LIABILITY TO ANY PARTY FOR ANY LOSS, DAMAGE, OR DISRUPTION CAUSED BY ERRORS OR OMISSIONS, WHETHER SUCH ERRORS OR OMISSIONS RESULT FROM NEGLIGENCE, ACCIDENT, OR ANY OTHER CAUSE.

Programming languages, technology

This article involves the following programming languages and technology:

  • Javascript
  • Node.js
  • hapi.js
  • HTTP

The following node modules and Javascript libraries are used:

  • hapi.js
  • inert
  • path (core module)
  • fs (core module)
  • rot13-transform
  • joi
  • boom
  • hapi-auth-basic

Table of Contents

The article is divided into the following sections:

  • Preparation, prerequisites
  • 1.: Streams
  • 2.: Validation
  • 3.: Validation using JOI object
  • 4.: Uploads
  • 5.: Cookies
  • 6.: Authentication
  • Closing words
  • License
  • References

1.: Streams

Photo by Cory Schadt on Unsplash

In this exercise an additional module will be installed. This module is rot13-transform, and it implements the ROT13 transformation using streams.

ROT13 (“rotate by 13 places”, sometimes hyphenated ROT-13) is a simple letter substitution cipher that replaces a letter with the 13th letter after it, in the alphabet. ROT13 is a special case of the Caesar cipher which was developed in ancient Rome. (Source: Wikipedia)

Install this module:

node install rot13-transform

The fs core module of Node.js is used. In this example the content of a text file (makemehapi-exercise-8.txt) is read up, and the ROT13 transformation is applied to the content of it (with the pipe() call, which I already mentioned in my earlier article about RxJS here). The result is then returned to the client. The implementation looks like the following:

2.: Validation

In this example Joi framework is used for validation. Joi is an object schema description language and validator for Javascript object.

npm install joi

In the following example path /chicken is defined and a parameter suffix named breed. The parameter is optional (which is indicated with the ? sign).

If we navigate with the browser to the /chickens path, we get the following:

Without the validation implementation (commenting out lines 28–34 and the , at the end of line 26) we get the following:

If we navigate to /chickens/Sussex, we get the following:

3.: Validation using JOI object

By using a Joi object custom validation rules can be specified in
paths, request payloads, and responses.

In the example below complex conditions are defined for the parameters:

  • isGuest is of type boolean, and a mandatory parameter
  • username is of type string and when isGuest is true, then username is mandatory (remember that isGuest is mandatory). (Otherwise username is not mandatory.)
  • accessToken is an alphanumeric string
  • password is an alphanumeric string

When a post request is sent to the path /login, then these validations are executed on the payload.

4.: Uploads

This example shows how to upload file to the server using Hapi.js. I enhanced the original solution with a HTML page with which it is possible to test the implemented server.

Some important points of this code example:

  • config has 2 properties: handler and payload
  • a Promise is returned in the handler
handler: (request, h) => {
return new Promise((resolve, reject) => {
<...>
});
},
<...>
  • the file can be accessed with request.payload.uploadedFile
  • uploadedFile is the name in the <input> tag of the HTML:
<input type="file" name="uploadedFile" size="40">
  • the implementation reacts on 3 events: data, end and error
request.payload.uploadedFile.on('data', (data) => {<...>request.payload.uploadedFile.on('end', () => {<...>request.payload.uploadedFile.on('error', (err) => {
  • file data is collected to a variable
let body = '';<...>body += data;
  • To accept the file as input and get the file as readable stream, the following is used:
payload: {
output: 'stream',
parse: true,
allow: 'multipart/form-data'
}
  • the example implementation does not save to file to the server, but prints some info about it, and the file contents as well

The implementation looks like the following:

In my environment the app works the following way:

5.: Cookies

This exercise is about handling cookies.

An HTTP cookie (also called web cookie, Internet cookie, browser cookie, or simply cookie) is a small piece of data sent from a website and stored on the user’s computer by the user’s web browser while the user is browsing. Cookies were designed to be a reliable mechanism for websites to remember stateful information (such as items added in the shopping cart in an online store) or to record the user’s browsing activity (including clicking particular buttons, logging in, or recording which pages were visited in the past). They can also be used to remember arbitrary pieces of information that the user previously entered into form fields such as names, addresses, passwords, and credit card numbers. (source: Wikipedia)

HTTP State Management Mechanism is defined in RFC 6265.

“[…] the HTTP Cookie and Set-Cookie header fields […] can be used by HTTP servers to store state […] at HTTP user agents, letting the servers maintain a stateful session over the mostly stateless HTTP protocol. […] Using the Set-Cookie header field, an HTTP server can pass name/value pairs and associated metadata (called cookies) to a user agent. When the user agent makes subsequent requests to the server, the user agent uses the metadata and other information to determine whether to return the name/value pairs in the Cookie header. […] the server indicates a scope for each cookie when sending it to the user agent. The scope indicates the maximum amount of time in which the user agent should return the cookie, the servers to which the user agent should return the cookie […]. (source: RFC 6265)

This implementation uses package boom which provides a set of utilities for returning HTTP errors.

npm install boom

In this example, the server responds to the following paths:

  • set-cookie: sets a cookie with key session
  • check-cookie: checks the cookies if there is one with the key session. If yes, then returns { user: 'hapi' }, otherwise returns an unauthorized error message

The solution for this exercise works the following way:

  • To use a cookie, it needs to be configured with server.state() call
server.state('session', {
path: '/',
encoding: 'base64json',
ttl: 10,
domain: 'localhost',
isSameSite: false,
isSecure: false,
isHttpOnly: false
});

The above code tells the server the following:

— the path used is /

— the cookie should be base64json encoded

— the cookie expires in 10 milliseconds (for example, other option would be ttl: null which would set session lifetime, meaning the cookie will be deleted by the browser when closed)

— the domain scope of the cookie is localhost

More details about the isSecure attribute:

The Secure attribute limits the scope of the cookie to “secure” channels (where “secure” is defined by the user agent). When a cookie has the Secure attribute, the user agent will include the cookie in an HTTP request only if the request is transmitted over a secure channel (typically HTTP over Transport Layer Security (TLS) [RFC2818]).

Although seemingly useful for protecting cookies from active network attackers, the Secure attribute protects only the cookie’s confidentiality. An active network attacker can overwrite Secure cookies from an insecure channel, disrupting their integrity (see Section 8.6 for more details). (Source)

More details about the isHttpOnly attrbute:

The HttpOnly attribute limits the scope of the cookie to HTTP requests. In particular, the attribute instructs the user agent to omit the cookie when providing access to cookies via “non-HTTP” APIs (such as a web browser API that exposes cookies to scripts).

Note that the HttpOnly attribute is independent of the Secure attribute: a cookie can have both the HttpOnly and the Secure attribute. (Source)

  • Navigation to the /set-cookie path will set a cookie with the key ‘session’ and the value { key: 'makemehapi' }. This is achieved with the following handler
handler: (request, h) => {
return h.response({
message : 'success'
}).state('session', { key : 'makemehapi' });
}
  • the cookie behaviour can be further configured at a route-level with the following
config: {
state: {
parse: true, // parse cookies and store in request.state
failAction: 'log' // may also be 'error' or 'log'
}
}
  • unauthorized error with the correct HTTP status can easily be returned with the help of an additional plugin called boom. This needs to be installed beforehand with npm install boom.
  • The check-cookie endpoint will have cookies received from the /set-cookie endpoint. If the session key is present in cookies then simply return { user: 'hapi' }, otherwise return an unauthorized access error. This is done with the following route configuration:
server.route({
method: 'GET',
path: '/check-cookie',
handler: (request, h) => {
var session = request.state.session;
var result;
if (session) {
result = { user : 'hapi' };
} else {
result = Boom.unauthorized('Missing authentication');
}

return result;
}
});

To be able to run this in the browser, I modified some things compared to the official solution:

  • I used process.env.IP and process.env.PORT as the IP address and port for the server
  • I used domain: process.env.IP in the server.state() call so that it will work in Cloud 9
  • I used ttl: null

The source code of the solution is the following:

6.: Authentication

Photo by Kyle Glenn on Unsplash

The Hapi plugin for handling basic authentication is called hapi-basic-auth:

npm install hapi-auth-basic

This is a basic authentication. With a help of a function we can authenticate a user and return if the authentication was successful or failed. (In this basic the password is a hard-coded plain text, but of course normally it should be stored encrypted.) In the example app, the username is hapi, the password is auth.

With server.auth.strategy('simple', 'basic', { validate }); we tell the server that it should use ‘simple’ auth. strategy, without setting it as default.

With server.auth.default('simple'); we tell the server that this is the default authentication strategy, all routes added afterwards will follow this one.

Closing words

I hope you have learned something new today. Thank you for reading this article.

License

The code snippets used in this article are based on the original solutions for the workshop, therefore I reproduce here the license of makemehapi workshop.

Copyright (c) 2012-2014, Walmart and other contributors.
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following
disclaimer in the documentation and/or other materials
provided with the distribution.
* The names of any contributors may not be used to endorse or
promote products derived from this software without specific
prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS AND CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
* * *The complete list of contributors can be found at: https://github.com/hapijs/makemehapi/graphs/contributors

Original version of the License:

References

--

--

László Harri Németh
The Coding Hype

Software developer. Python, SQL, ABAP, Swift, Javascript, Java, C, C++, Ruby, noSQL, Bash, Linux. http://nlharri.hu http://github.nlharri.hu hello@nlharri.hu