@leisurelink/trusted-app
v4.1.1
Published
An Expressjs derived engine for web applications participating in LeisureLink's federated security.
Downloads
13
Keywords
Readme
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 onenv-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 - assertsinfo
,warn
, anderror
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 Expressrequest
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
usesmorgan
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 meanauthentic-api
becauseauthentic-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 theAuthScope
class configured according to youroptions
..auth.client
– an instance of theAuthenticClient
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:
httpPort
: number, required – the port on which the app will accept HTTP requests.httpsPort
: number, optional – the port on which the app will accept HTTPS requests.tlsOptions
: object, optional – TLS options passed as-is to the underlyinghttps.createServer
, see nodejs.org's documentation for more info.
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 acorrelation-id
across calls to micro-services; if used it must followdomainCorrelation
andremoteAuth
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'sauth-token
is propagated with the call; establishes the user's authority.