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
– represents the local process's authority. -
remote-authority
– represents the remote process's authority. -
user-authority
– represents the end user's authority within the system.
Install
npm install --save @leisurelink/trusted-endpoint
Use
Plugins/Middleware
- Hapi-Plugin: Hapi-voucher
Import It
var endpoint = require('@leisurelink/trusted-endpoint');
All subsequent examples assume this import!
API
@leisurelink/trusted-endpoint
exports the following classes:
-
TrustedEndpoint
– A utility class for reasoning about trust in relation to an HTTP Signature and LeisureLink's federated security and claims model. -
TrustedEndpointCache
– A subclass ofTrustedEndpoint
extended in order to support a short-term, local-memory cache for resolved and trusted public keys and claims. -
KeyId
– 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
– 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 – an object specifying:-
keyId
: string or KeyId, required – key identifier used to authenticate the current process as a system principal/trusted-endpoint. SeeKeyId
later in this readme. -
scope
:AuthScope
, required – an authorization scope used by the endpoint to verifyauth-tokens
. -
resolveEndpointKey
: function, required – a function with signatureresolveEndpointKey(lang, keyId): Promise
; invoked when the endpoint needs to lookup the private key identified bykeyId
. SeeEndpoint Key Resolver
later in this readme. -
resolveEndpointClaims
: function, required – a function with signatureresolveEndpointClaims(lang, principalId): Promise
; invoked when the endpoint needs to resolve another trusted endpoint's claims. SeeEndpoint Claims Resolver
later in this readme. -
lang
: string, required – the BCP 47 language code identifying the language used when resolving endpoint claims. -
useCache
: bool, optional – indicates whether the instance should cache resolved keys and claims. Default: false. -
keyCacheTimeoutSeconds
: intenger, optional – the lifespan of cached keys. Default: 0 - forever. -
localAuthTimeoutSeconds
: intenger, optional – the lifespan of the local endpoint's authorization in the cache. Default: 15 minutes. -
remoteAuthTimeoutSeconds
: intenger, optional – the lifespan of the remote endpoint's authorization in the cache. Default: 5 minutes.
-
-
useAsSingleton
: bool, optional – indicates whether the create instance should be used as the module's singleton.
returns: A new TrustedEndpoint instance.
example:
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)
.
returns:
- The
TrustedEndpoint
instance acting as the module's singleton.
.getLocalAuth()
Gets the current process' local authority as an instance of AuthContext
returns:
- A
Promise
that is resolved with an instance ofAuthContext
, created on the current process'auth-token
, if such can be verified, otherwise the promise is rejected with an appropriate error.
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 – an HTTP request object, as provided to the underlying HTTP Server'srequest
event handlers.
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 ofAuthContext
representing the remote authority. This member is only present if the request bore a valid Authorization header. -
remoteUser
: an instance ofAuthContext
representing the user authority. This member is only present if the request was accompanied with a human user'sauth-token
.
-
examples:
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 – the BCP 47 language code identifying the suggested language for any textual output rendered for a human user. -
keyId
: string orKeyId
, required – 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:
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 – the BCP 47 language code identifying the suggested language for any textual output rendered for a human user. -
principalId
: string, required – the endpoint's identity.
returns:
- A promise, resolved with an
auth-token
upon success, otherwise rejected with an error.
example:
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);
}
});
});
}