@leisurelink/trusted-app

4.1.1 • Public • Published

trusted-app

A convention-based web framework, building on Express, for APIs and websites participating in LeisureLink's federated security.

Why

Whereas Express 4 is an un-opinionated web framework for Node.js, trusted-app must have a few opinions in order to satisfy LeisureLink's security principles. Therefore, while it builds on Express, it adds more convention to the process of starting up an application.

The trusted part of trusted-app means that the application participates in LeisureLink's federated security; therefore, trusted-app comes with the appropriate middleware to ensure callers are authenticated, authorized, their actions are non-repudiable, and activity is audited.

We also think that trusted means well behaved, so trusted-app establishes conventional error handlers late in the middleware chain to ensure that clients get predictable responses, even in the face of unexpected errors.

Beyond these basic notions of making an application trusted, this version of trusted-app endeavors to stay out of your way so you can use the underlying Express the way you want.

Trust

By trust, we really mean secure. Moreover, we trust that our callers are secure because they provide us with evidence of such with each request.

We use a combination of HTTP Signature and JWT to establish trust; you may want to familiarize yourself with these, but it is not a pre-requisite.

Install

npm install @leisurelink/trusted-app

Quick Start

Simple Use Adding Nothing

Using trusted-app is very similar to using express:

var trusted = require('@leisurelink/trusted-app');

// trusted is-a express app.
let app = trusted();

app.get('/hello/:who', require('./routes/hello/get'));

app.start(3000);
app.shutdownOnProcessEvents('SIGTERM', 'SIGINT');

Of course the example above adds very little to bald Express; what would be the point of that?

Making it Trusted

To make the application trusted, we need to configure and wireup some middleware. For this we'll need a few keys because trust is accomplished in-part by digital signature:

var path = require('path');
var trusted = require('@leisurelink/trusted-app');
var Loggins = require('@leisurelink/skinny-loggins');

// trusted is-a express app.
let app = trusted();

// establish who we are as an endpoint, and who we trust
let trustedAuthorityUri = 'http://api.leisurelink.com:2999/';
let endpointPrivateKeyFile = path.normalize(path.join(__dirname, '../test-key.pem'));
let trustedPublicKeyFile = path.normalize(path.join(__dirname, '../test-key.pub'));

let trustOptions = {
  endpointKeyId: 'test/me', // this app's HTTP Signature identity
  endpointPrivateKeyFile,   // the private key this app uses for HTTP signatures
  trustedAuthorityUri,      // the URI of our trusted authority
  trustedIssuer: 'test',    // the trusted JWT issuer (authority's well-known name)
  trustedAudience: 'test',  // this is the JWT audience (non-matching tokens are untrusted)
  trustedPublicKeyFile      // this is the JWT issuer's public key, used to verify signatures.
};

app.establishLogger(new Loggins());
app.establishTrust(trustOptions);

app.get('/hello/:who',

  // demand that callers are also trusted endpoints
  trusted.demand.authenticatedEndpoint,

  require('./routes/hello/get')
  );

app.start(3000);
app.shutdownOnProcessEvents('SIGTERM', 'SIGINT');

With trust established, and the new demand middleware in place, the API will enforce security.

Don't be intimidated by the extra code in this example — most of it deals with settings required to configure the trust relationship between the app and the trusted authority (aka authentic-api). Notably, version 3+ of this module no longer has a direct dependency on env-configurator, but most likely your app should; never hard code these values, this example does so for brevity.

Debug

If you think you're getting unexpected results, turn on debug to get some insight into what's going on:

> DEBUG=trusted* node app.js

Use

trusted-app extends Express by adding to it's API.

.establishLogger(logger, skip)

Associates the specified logger with the application. A logging mechanism is required by several security related middleware. The logger will be exposed on the app as a readonly property .log.

arguements:

  • logger : object, required – an object exposing conventional methods for logging - asserts info, warn, and error methods are present on the object.
  • skip : function, optional – a function that determines if a particular request should be logged. The function is given the Express request object and should return a truthy response if the logging should be skipped.

returns:

  • The app for chaining calls.

example:

var Loggins = require('@leisurelink/skinny-loggins');

app.logger(new Loggins(), (req) => {
  // skip logging for the heath-check route; it occurs too frequently
  return req.baseUrl === '/health';
});

app.log.info('A logger is attached!');

NOTE: .establishLogger uses morgan to shuttle messages from Express to the logger. If you need to customize morgan's behavior, don't use this method. However, be aware that the security middleware expects the app to have a .log property that resolves to the logger — you may have to assign it directly.

.establishTrust(options)

Sets up security related middleware using the specified options, ensuring the app's secure participation in LeisureLink's federated security.

NOTE: In generic terms, a trusted authority is an API we trust to sign auth-tokens. The only implementation of trusted authority is LeisureLink's authentic-api; therfore you should record in your brain-hole that when we say trusted authority we mean authentic-api because authentic-api is-a trusted authority.

arguements:

  • options : object, required – an object specifying trust related configuration options.
    • endpointKeyId : string, required – the identity of the application, used to compose digital signatures when this app (as a trusted-endpoint) communicates with other services.
    • endpointPrivateKeyFile : string, required – file system path to the key this application uses when producing digital signatures. The corresponding public key must be registered with the trusted authority before signatures will be trusted.
    • trustedAuthorityUri : string, required – the base-URI of the trusted authority used to authenticate and authorize callers.
    • trustedIssuer : string, required – specifies the trusted JWT issuer; this is the well-known name of the trusted authority, tokens bearing a non-matching issuer are untrusted.
    • trustedAudience : string, required – specifies the trusted JWT audience; tokens bearing non-matching audience are untrusted.
    • trustedPublicKeyFile : string, required – file system path to the trusted authority's public key; used to verify the trusted authority's digital signature.

returns:

  • The app for chaining calls.

When .establishTrust returns successfully, app has additional trust-related properties:

  • .auth.scope – an instance of the AuthScope class configured according to your options.
  • .auth.client – an instance of the AuthenticClient class
// Logger must already be established for this to succeed!

app.establishTrust({
  endpointKeyId: 'this-app/self',
  endpointPrivateKeyFile: '/path/to/private-key.pem',
  trustedAuthorityUri: `https://uri.to.trusted-authority/`,
  trustedIssuer: 'test',
  trustedAudience: 'test',
  trustedPublicKeyFile: '/path/to/issuer-public-key.pub'
});

.start(httpPort, httpsPort, tlsOptions)

Starts the underlying HTTP(S) server(s) on the specified ports.

arguements:

returns:

  • The app for chaining calls.

events:

  • starting – emitted before the app create's the HTTP(S) server(s)
  • listening – emitted when a server, either HTTP or HTTPS begins listening on a port.

example (without TLS):

app.start(3000);

example (with TLS):

app.start(3000, 3443, {
  key: fs.readFileSync('my/tls/key.pem'),
  cert: fs.readFileSync('my/tls/cert.pem')
});

.shutdown(exitProcess, exitCode, suicideTimeoutMs)

Attempts a normal/friendly shutdown; aborting after the specified suicideTimeoutMs.

arguements:

  • exitProcess : _boolean, optional – indicates whether the process should exit as a result of the shutdown. Default false.
  • exitCode : number, optional – specifies an exit code to use if exiting the process as a result of shutting down. Default 0.
  • suicideTimeoutMs : number, optional – specifies the number of milliseconds to wait for the app to shut down normally before forcing a process exit. Default 15000 milliseconds.

events:

  • shutting-down – emitted when the app begins it's shutdown process
app.shutdown(true, -1);

.shutdownOnProcessEvents(evt, ...)

Hooks the specified process events so that when the event is observed the app's .shutdown() method is called.

arguements:

  • evt : string(s) – the name of one or more process events to monitor.

returns:

  • The app for chaining calls.

example:

app.shutdownOnProcessEvents('SIGTERM', 'SIGINT');

Middleware and Demands

trusted-app comes with middleware that you may use a la carte. These are exported under the .middleware property:

  • .middleware.domainContext – middleware that establishes a domain context; if used it should be very early in the middleware chain.
  • .middleware.domainCorrelation – middleware that helps propagate a correlation-id across calls to micro-services; if used it must follow domainCorrelation and remoteAuth in the middleware chain.
  • .middleware.errorHandler – middleware that performs LeisureLink's conventional error handling.
  • .middleware.localAuth – middleware that establishes the local service's authority.
  • .middleware.remoteAuth – middleware that establishes the remote endpoint's authority and if a user's auth-token is propagated with the call; establishes the user's authority.

Readme

Keywords

none

Package Sidebar

Install

npm i @leisurelink/trusted-app

Weekly Downloads

1

Version

4.1.1

License

none

Last publish

Collaborators

  • uniqname
  • blake1321
  • jouellette
  • lhirschi
  • flitbit
  • jtowner
  • twindagger
  • leisurelink-robot