npm package: @leisurelink/trusted-endpoint
Description: Nodejs utility classes for participating in LeisureLink's federated security as a trusted endpoint.
Author: Phillip Clark
Latest version: 6.0.0

# trusted-endpoint

Nodejs utility classes for participating in LeisureLink's federated security as a _trusted endpoint_.

## Background

### Reference to Key Terms

LeisureLink's federated security is a _claims-based security model_. Key definitions and terminology can be found in the [readme's for `auth-context`]() and [`claims`]().

### Scope of this Module

This module's purpose is to encapsulate the data and logic necessary for an endpoint to recognize _trusted calls_ and establish the authority of each _security principal_ involved in an operation.

It is important to recognize that all applications have _local authority_. In other words, an application always operates within its own authority.

If an application is callable, such as an API, in the scope of responding to such call, the application should establish the caller's authority. We refer to the caller's authority as _remote authority_.

There are two primary kinds of users in LeisureLink's federated security; _system users_ and _human users_. System users can be many kinds of things; they may be other micro-services in our service-inventory, they may be websites we maintain, they may be partner API's that call our own API's. Regardless of the nature of the system, when it participates in our _trust-model_ we call it a _trusted-endpoint_.

Any system that makes use of LeisureLink's platform **must** do so via a _trusted-endpoint_. When this requirement is met, it means that our systems only accept _trusted-calls_.

If a _trusted-call_ is being made in response to a human user's activity, such as when website activity flows through to our API's, those _trusted-calls_ should include the end-user's _auth-token_. By decoding the user's _auth-token_, a _trusted-endpoint_ establishes _user authority_.

`trusted-endoint` recognizes _trusted-calls_ and may establish the following:

* `local-authority` &ndash; represents the local process's authority.
* `remote-authority` &ndash; represents the remote process's authority.
* `user-authority` &ndash; represents the end user's authority within the system.

## Install

```bash
npm install --save @leisurelink/trusted-endpoint
```

## Use

## Plugins/Middleware

  - Hapi-Plugin: [Hapi-voucher](https://github.com/LeisureLink/hapi-voucher)

### Import It

```javascript
var endpoint = require('@leisurelink/trusted-endpoint');
```

> **All subsequent examples assume this import!**


### API

`@leisurelink/trusted-endpoint` exports the following classes:

* [`TrustedEndpoint`](#user-content-trustedendpoint-class) &ndash; A utility class for reasoning about trust in relation to an HTTP Signature and LeisureLink's federated security and _claims model_.
* [`TrustedEndpointCache`](#user-content-trustedendpointcache-class) &ndash; A subclass of `TrustedEndpoint` extended in order to support a short-term, local-memory cache for resolved and trusted public keys and claims.
* [`KeyId`](#user-content-keyid-class) &ndash; A utility class for parsing key identifiers in HTTP Signatures and reasoning about the parts that make up a key identifier in LeisureLink's federated security.
* [`MalformedKeyIdError`](#user-content-malformedkeyiderror-class) &ndash; A specialized error class thrown when a parsed KeyId is malformed.

`@leisurelink/trusted-endpoint` may be initialized and used as a singleton. Such use is discouraged.

#### .create(options, useAsSingleton)

Creates a new instance of the `TrustedEndpoint` class using the specified `options`. Optionally uses the create instance as the module's singleton.

_arguments:_
* `options` : _object, **required**_ &ndash; an object specifying:
  * `keyId` : _string or KeyId, **required**_ &ndash; key identifier used to authenticate the current process as a _system principal_/_trusted-endpoint_. See [`KeyId` later in this readme](#user-content-keyid-class).
  * `scope` : _[`AuthScope`](https://github.com/LeisureLink/auth-context#user-content-authscope-class), required_ &ndash; an authorization scope used by the endpoint to verify `auth-tokens`.
  * `resolveEndpointKey` : _function, **required**_ &ndash; a function with signature `resolveEndpointKey(lang, keyId): Promise`; invoked when the endpoint needs to lookup the private key identified by `keyId`. See [`Endpoint Key Resolver` later in this readme](#user-content-endpoint-key-resolver).
  * `resolveEndpointClaims` : _function, **required**_ &ndash; a function with signature `resolveEndpointClaims(lang, principalId): Promise`; invoked when the endpoint needs to resolve another trusted endpoint's claims. See [`Endpoint Claims Resolver` later in this readme](#user-content-endpoint-claims-resolver).
  * `lang` : _string, **required**_ &ndash; the [BCP 47 language code](https://tools.ietf.org/html/bcp47) identifying the language used when resolving endpoint claims.
  * `useCache`: _bool, **optional**_ &ndash; indicates whether the instance should cache resolved keys and claims. Default: false.
  * `keyCacheTimeoutSeconds`: _intenger, **optional**_ &ndash; the lifespan of cached keys. Default: 0 - forever.
  * `localAuthTimeoutSeconds`: _intenger, **optional**_ &ndash; the lifespan of the local endpoint's authorization in the cache. Default: 15 minutes.
  * `remoteAuthTimeoutSeconds`: _intenger, **optional**_ &ndash; the lifespan of the remote endpoint's authorization in the cache. Default: 5 minutes.
* `useAsSingleton` : _bool, **optional**_ &ndash; indicates whether the create instance should be used as the module's singleton.

_returns:_
A new TrustedEndpoint instance.

_example:_

```javascript
var auth = require('@leisurelink/auth-context');
var trusted = require('@leisurelink/trusted-endpoint');

let options = {
  issuer: 'test',                      // JWT issuer we trust
  audience: 'test',                    // JWT audience we expect
  issuerKeyFile: './test/test-key.pub' // The issuer's public key, so we can verify the issuer's digital signature
};

let scope = new auth.AuthScope(options);
let endpoint = trusted.create({
  keyId: 'my/key',
  scope,
  resolveEndointKey: function (lang, keyId) { /* hrm, gotta resolve the key here! */ },
  resolveEndointClaims: function (lang, principalId) { /* hrm, gotta resolve the claims here! */ }
})
```

#### .singleton()

Gets the `TrustedEndpoint` instance that keeps the module's state.

> NOTE: It is an error to use the module as-a `TrustedEndpoint` prior to initializing the module for such use. This restriction is enforced by direct and indirect calls to the `.singleton()` method. Initialization requires a call to [`.create(options, true)`](#user-content-create-options-useassingleton).

_returns:_
* The `TrustedEndpoint` instance acting as the module's singleton.

#### .getLocalAuth()

Gets the current process' _local authority_ as an instance of [`AuthContext`](https://github.com/LeisureLink/auth-context#user-content-authcontext-class)

_returns:_
* A `Promise` that is resolved with an instance of [`AuthContext`](https://github.com/LeisureLink/auth-context#user-content-authcontext-class), created on the current process' `auth-token`, if such can be verified, otherwise the promise is rejected with an appropriate error.

```javascript
endpoint.getLocalAuth()
  .then(ctx => {
    console.log(`Got claims for ${ctx.principalId} that are valid until ${ctx.expiresAt}.`);
  })
  .catch(err => {
    console.log(`Oops, something unexpected happened: ${err}.`);
  });
```

#### .getRemoteAuth(request)

For the specified HTTP request, gets the authority associated with the request.

_arguments:_

* `request`: _object, required_ &ndash; an HTTP request object, [as provided to the underlying HTTP Server's `request` event handlers](https://nodejs.org/dist/latest-v4.x/docs/api/http.html).

_returns:_

* A `Promise` that is resolved with a success object upon success and rejected with an error if the operation fails. The success object has the following properties:
  * `remoteEndpoint`: an instance of [`AuthContext`](https://github.com/LeisureLink/auth-context#user-content-authcontext-class) representing the _remote authority_. This member is only present if the request bore a valid _Authorization_ header.
  * `remoteUser`: an instance of [`AuthContext`](https://github.com/LeisureLink/auth-context#user-content-authcontext-class) representing the _user authority_. This member is only present if the request was accompanied with a human user's `auth-token`.

_examples:_

```javascript
var AuthContext = require('@leisurelink/auth-context').AuthContext;

// ...

endpoint.getRemoteAuth(request)
  .then(res => {
    if (AuthContext.isContext(res.remoteEndpoint)) {
      let ep = res.remoteEndpoint;
      console.log(`Remote endpoint is a trusted ${ep.kind}: ${ep.principalId}.`);
    }
    if (AuthContext.isContext(res.remoteUser)) {
      let usr = res.remoteUser;
      console.log(`Remote user is a trusted ${usr.kind}: ${usr.principalId}.`);
    }
  })
  .catch(err => {
    console.log(`Oops, something unexpected happened: ${err}.`);
  });

```

#### Endpoint Key Resolver

An _endpoint key resolver_ is a callback function specified as an option when creating new `TrustedEndpoint` instances. The resolver is called when a key needs to be resolved.

_arguments:_
* `lang` : _string, required_ &ndash; the [BCP 47 language code](https://tools.ietf.org/html/bcp47) identifying the suggested language for any textual output rendered for a human user.
* `keyId` : _string or `KeyId`, required_ &ndash; the key's identity. This value may be an instance of KeyId. In either case it is coercible to a string.

_returns:_
* A promise, resolved with the public key in PEM format upon success, otherwise rejected with the error that occurred.

 _example:_

```javascript
var assert = require('assert');
var fs = require('fs');
var path = require('path');

var AuthenticClient = require('@leisurelink/authentic-client');
var trusted = require('@leisurelink/trusted-endpoint');

let keyFile = path.normalize(path.join(__dirname, '../test/test-key.pem'));
let key = fs.readFileSync(keyFile);

let client = new AuthenticClient('http://localhost:2999', 'my/key', key);

function resolveEndpointKey(lang, keyId) {
  assert.ok(typeof(lang) === 'string', 'lang must be specified');
  assert.ok(typeof(keyId) === 'string' || keyId instanceof trusted.KeyId, 'lang must be specified');
  keyId = (typeof(keyId) === 'string') ? trusted.KeyId.parse(keyId) : keyId;
  return new Promise((resolve, reject) => {
    client.getEndpointKey(lang, keyId.principalId, keyId.keyId, function(err, res, body) {
      if (err) {
        reject(err);
      } else {
        resolve(body.result);
      }
    });
  });
}
```

#### Endpoint Claims Resolver

An _endpoint claim resolver_ is a callback function specified as an option when creating new `TrustedEndpoint` instances. The resolver is called when an endpoint's claims need to be resolved.

_arguments:_
* `lang` : _string, required_ &ndash; the [BCP 47 language code](https://tools.ietf.org/html/bcp47) identifying the suggested language for any textual output rendered for a human user.
* `principalId` : _string, required_ &ndash; the endpoint's identity.

_returns:_
* A promise, resolved with an `auth-token` upon success, otherwise rejected with an error.

 _example:_

```javascript
var assert = require('assert');
var fs = require('fs');
var path = require('path');

var AuthenticClient = require('@leisurelink/authentic-client');
var trusted = require('@leisurelink/trusted-endpoint');

let keyFile = path.normalize(path.join(__dirname, '../test/test-key.pem'));
let key = fs.readFileSync(keyFile);

let client = new AuthenticClient('http://localhost:2999', 'my/key', key);

function resolveEndpointClaims(lang, principalId) {
  return new Promise((resolve, reject) => {
    client.getEndpointClaims(lang, principalId, function(err, res, body) {
      if (err) {
        reject(err);
      } else {
        resolve(body.result);
      }
    });
  });
}
```