vandium
Advanced tools
Comparing version 6.1.0 to 7.0.0-Beta1
# Change Log | ||
## 7.0.0 (TBD) | ||
New: | ||
* `apigateway` handler that contains defaults to reduce the code required to | ||
implement a lambda handler. | ||
* Support for JWKS keys via `useJwks()` | ||
* `createHandler()` now the preferred way to create lambda handlers and allows | ||
the use of hooks. | ||
* Handler hooks to inspect or stub functionality for testing purposes. Can be | ||
used to bypass JWT validation in unit tests when private keys are unknown. | ||
## 6.1.0 (2020-02-13) | ||
@@ -4,0 +17,0 @@ |
@@ -1,339 +0,80 @@ | ||
const MethodHandler = require( './method' ); | ||
const JWTValidator = require( './jwt' ); | ||
const Protection = require( './protection' ); | ||
const constants = require( './constants' ); | ||
const TypedHandler = require( '../typed' ); | ||
const MethodHandler = require( './method' ); | ||
const responseProcessor = require( './response' ); | ||
const BaseAPIHandler = require( './base_api_handler' ); | ||
const { processCookies } = require( './cookies' ); | ||
class APIHandler extends BaseAPIHandler { | ||
const { processBody } = require( './body' ); | ||
constructor( options = {} ) { | ||
const { processHeaderValue } = require( './helper' ); | ||
super( options ); | ||
const { isFunction, parseBoolean } = require( '../../utils' ); | ||
this.methodHandlers = {}; | ||
this._currentMethodHandler = null; | ||
const { types } = require( '../../validation' ); | ||
this.pipeline.stage( 'methodValidation', (state) => { | ||
const Pipeline = require( '../pipeline' ); | ||
let { event } = state; | ||
const PREPROCESSOR_PIPELINE_STAGES = [ | ||
let method = event.httpMethod; | ||
'methodValidation', | ||
'eventNormalization', | ||
'bodyProcessing', | ||
'protection', | ||
'cookies', | ||
'jwt', | ||
'validation', | ||
'extractExecutor' | ||
]; | ||
let methodHandler = this.methodHandlers[ method ]; | ||
class APIHandler extends TypedHandler { | ||
if( !methodHandler ) { | ||
constructor( options = {} ) { | ||
super( 'apigateway', options ); | ||
this._initPipeline(); | ||
this.authorization(); | ||
this.bodyEncoding( options.bodyEncoding ); | ||
this._headers = {}; | ||
this.protection( options.protection ); | ||
this.methodHandlers = {}; | ||
this._onErrorHandler = (err) => err; | ||
this.afterFunc = function() {}; | ||
} | ||
authorization( authConfig ) { | ||
return this.jwt( authConfig ); | ||
} | ||
jwt( options = {} ) { | ||
const jwt = new JWTValidator( options ); | ||
this.pipeline.stage( 'jwt', ( { event } ) => { | ||
jwt.validate( event ); | ||
}); | ||
return this; | ||
} | ||
formURLEncoded( enabled = true ) { | ||
return this.bodyEncoding( enabled ? 'formURLEncoded' : 'auto' ); | ||
} | ||
skipBodyParse() { | ||
return this.bodyEncoding( 'none' ); | ||
} | ||
bodyEncoding( encoding = 'auto' ) { | ||
// switch( encoding ) { | ||
// | ||
// case 'formURLEncoded': | ||
// case 'auto': | ||
// case 'none': | ||
// //this._bodyEncoding = encoding; | ||
// break; | ||
// | ||
// default: | ||
// throw new Error( `Unsupported body encoding type: ${encoding}` ); | ||
// } | ||
this.pipeline.stage( 'bodyProcessing', (state) => { | ||
const { event } = state; | ||
if( event.body ) { | ||
event.rawBody = event.body; | ||
event.body = processBody( event.body, encoding ); | ||
} | ||
}); | ||
return this; | ||
} | ||
headers( values = {} ) { | ||
for( let name in values ) { | ||
this.header( name, values[ name ] ); | ||
throw new Error( 'handler not defined for http method: ' + method ); | ||
} | ||
return this; | ||
} | ||
state.extra = { method, methodHandler }; | ||
}); | ||
} | ||
header( name, value ) { | ||
currentMethodHandler() { | ||
processHeaderValue( this._headers, name, value ); | ||
if( !this._currentMethodHandler ) { | ||
return this; | ||
} | ||
throw new Error( 'Method not selected' ); | ||
} | ||
protection( options ) { | ||
return this._currentMethodHandler; | ||
} | ||
this._protection = new Protection( options ); | ||
addMethodsToHandler( lambdaHandler ) { | ||
return this; | ||
} | ||
super.addMethodsToHandler( lambdaHandler ); | ||
cors( options = {} ) { | ||
[ | ||
'jwt', | ||
'authorization', | ||
'handler' | ||
const headerListValue = ( value ) => { | ||
].forEach( (handlerMethod) => this.addlambdaHandlerMethod( handlerMethod, lambdaHandler ) ); | ||
if( Array.isArray( value ) ) { | ||
constants.HTTP_METHODS.forEach( (methodType) => { | ||
value = value.join( ', ' ); | ||
} | ||
this.addlambdaHandlerMethod( methodType, lambdaHandler ); | ||
this.addlambdaHandlerMethod( methodType.toLowerCase(), lambdaHandler ); | ||
}); | ||
} | ||
return value; | ||
}; | ||
_addHandler( type, ...args ) { | ||
this.header( 'Access-Control-Allow-Origin', options.allowOrigin ); | ||
this.header( 'Access-Control-Allow-Credentials', options.allowCredentials ); | ||
this.header( 'Access-Control-Expose-Headers', headerListValue( options.exposeHeaders ) ); | ||
this.header( 'Access-Control-Max-Age', options.maxAge ); | ||
this.header( 'Access-Control-Allow-Headers', headerListValue( options.allowHeaders ) ); | ||
const methodHandler = new MethodHandler(); | ||
return this; | ||
} | ||
if( args.length > 1 ) { | ||
onError( onErrorHandler ) { | ||
methodHandler.setValidation( args[ 0 ] ); | ||
methodHandler.setHandler( args[ 1 ] ); | ||
} | ||
else if( args.length === 1 ) { | ||
this._onErrorHandler = onErrorHandler; | ||
methodHandler.setHandler( args[ 0 ] ); | ||
} | ||
return this; | ||
} | ||
this.methodHandlers[ type ] = methodHandler; | ||
this._currentMethodHandler = methodHandler; | ||
validation( functionOrOptions ) { | ||
let options = functionOrOptions; | ||
if( isFunction( functionOrOptions ) ) { | ||
options = functionOrOptions( types ); | ||
} | ||
this.currentMethodHandler.setValidation( options ); | ||
return this; | ||
} | ||
handler( handler ) { | ||
this.currentMethodHandler.setHandler( handler ); | ||
return this; | ||
} | ||
onResponse( onResponseHandler ) { | ||
this.currentMethodHandler.setOnResponse( onResponseHandler ); | ||
return this; | ||
} | ||
addMethodsToHandler( lambdaHandler ) { | ||
super.addMethodsToHandler( lambdaHandler ); | ||
[ | ||
'jwt', | ||
'authorization', | ||
'formURLEncoded', | ||
'header', | ||
'headers', | ||
'protection', | ||
'cors', | ||
'onError', | ||
'onResponse', | ||
'validation', | ||
'handler' | ||
].forEach( (handlerMethod) => this.addlambdaHandlerMethod( handlerMethod, lambdaHandler)); | ||
constants.HTTP_METHODS.forEach( (methodType) => { | ||
this.addlambdaHandlerMethod( methodType, lambdaHandler ); | ||
this.addlambdaHandlerMethod( methodType.toLowerCase(), lambdaHandler ); | ||
}); | ||
} | ||
executePreprocessors( state ) { | ||
super.executePreprocessors( state ); | ||
//execute pipeline | ||
this.pipeline.executorSync().run( state ); | ||
} | ||
async processResult( result, context, { methodHandler } ) { | ||
const responseObject = responseProcessor.processResult( result, context, this._headers ); | ||
return await this.processResponse( responseObject, methodHandler ); | ||
} | ||
async processError( error, context, { methodHandler } ) { | ||
let updatedError = await this._onErrorHandler( error, context.event, context ); | ||
if( updatedError ) { | ||
error = updatedError; | ||
} | ||
const responseObject = responseProcessor.processError( error, this._headers ); | ||
return await this.processResponse( responseObject, methodHandler ); | ||
} | ||
/** | ||
* Single conduit to processing responses | ||
*/ | ||
async processResponse( responseObject, methodHandler ) { | ||
const result = await methodHandler.onResponse( responseObject.result ); | ||
return { result }; | ||
} | ||
get currentMethodHandler() { | ||
if( !this._currentMethodHandler ) { | ||
throw new Error( 'Method not selected' ); | ||
} | ||
return this._currentMethodHandler; | ||
} | ||
_addHandler( type, ...args ) { | ||
const methodHandler = new MethodHandler(); | ||
if( args.length > 1 ) { | ||
methodHandler.setValidation( args[ 0 ] ); | ||
methodHandler.setHandler( args[ 1 ] ); | ||
} | ||
else if( args.length === 1 ) { | ||
methodHandler.setHandler( args[ 0 ] ); | ||
} | ||
this.methodHandlers[ type ] = methodHandler; | ||
this._currentMethodHandler = methodHandler; | ||
return this; | ||
} | ||
_initPipeline() { | ||
this.pipeline = new Pipeline( PREPROCESSOR_PIPELINE_STAGES ); | ||
this.pipeline.stage( 'methodValidation', (state) => { | ||
let { event } = state; | ||
let method = event.httpMethod; | ||
let methodHandler = this.methodHandlers[ method ]; | ||
if( !methodHandler ) { | ||
throw new Error( 'handler not defined for http method: ' + method ); | ||
} | ||
state.extra = { method, methodHandler }; | ||
}); | ||
this.pipeline.stage( 'eventNormalization', ( { event }) => { | ||
event.queryStringParameters = event.queryStringParameters || {}; | ||
event.multiValueQueryStringParameters = event.multiValueQueryStringParameters || {}; | ||
event.pathParameters = event.pathParameters || {}; | ||
}); | ||
this.pipeline.stage( 'protection', ( { event }) => { | ||
this._protection.validate( event ); | ||
}); | ||
this.pipeline.stage( 'cookies', ( { event }) => { | ||
event.cookies = processCookies( event.headers ); | ||
}); | ||
this.pipeline.stage( 'validation', ( { event, extra: { methodHandler } } ) => { | ||
methodHandler.validator.validate( event ); | ||
}); | ||
this.pipeline.stage( 'extractExecutor', (state) => { | ||
const { extra: { methodHandler } } = state; | ||
state.executor = methodHandler.executor; | ||
}); | ||
} | ||
return this; | ||
} | ||
} | ||
@@ -340,0 +81,0 @@ |
const APIHandler = require( './api_handler' ); | ||
function createHandler( options ) { | ||
const APIGateway = require( './apigateway' ); | ||
return new APIHandler( options ).createLambda(); | ||
function api( options ) { | ||
return new APIHandler( options ).createLambda(); | ||
} | ||
module.exports = createHandler; | ||
function apigateway( handler ) { | ||
return new APIGateway().handler( handler ); | ||
} | ||
module.exports = { | ||
api, | ||
apigateway, | ||
}; |
@@ -1,8 +0,8 @@ | ||
const utils = require( '../../utils' ); | ||
const { applyValues, parseBoolean, valueFromPath } = require( '../../utils' ); | ||
const jwt = require( '../../jwt' ); | ||
const { decode, formatPublicKey, resolveAlgorithm, validateXSRF } = require( '../../jwt' ); | ||
const jwkToPem = require('jwk-to-pem'); | ||
const resolveAlgorithm = jwt.resolveAlgorithm; | ||
const { getConfig: getAuthConfig } = require( '../../auth' ); | ||
@@ -17,3 +17,3 @@ const DEFAULT_JWT_TOKEN_PATH = 'headers.Authorization'; | ||
return utils.applyValues( options[ name ], ...otherValues ); | ||
return applyValues( options[ name ], ...otherValues ); | ||
} | ||
@@ -23,10 +23,10 @@ | ||
let value = optionValue( options, name, ...otherValues ); | ||
let value = optionValue( options, name, ...otherValues ); | ||
if( !value ) { | ||
if( !value ) { | ||
throw new Error( `missing required jwt configuration value: ${name}` ); | ||
} | ||
throw new Error( `missing required jwt configuration value: ${name}` ); | ||
} | ||
return value; | ||
return value; | ||
} | ||
@@ -36,98 +36,104 @@ | ||
constructor( options = {} ) { | ||
constructor( options = {} ) { | ||
if( options === false ) { | ||
if( options === false || options === true ) { | ||
options = { enabled: false }; | ||
} | ||
options = { enabled: options }; | ||
} | ||
else { | ||
const { jwk } = options; | ||
const { jwt } = getAuthConfig(); | ||
if( jwk ) { | ||
options = { | ||
const { alg, use } = jwk; | ||
...(jwt || {}), | ||
...options, | ||
} | ||
} | ||
if( alg !== 'RS256' ) { | ||
const { jwk } = options; | ||
throw new Error( 'Unsupported algorithm in JKS: ' + alg ); | ||
} | ||
if( jwk ) { | ||
if( use !== 'sig' ) { | ||
const { alg, use } = jwk; | ||
throw new Error( 'Key is not set to signature use' ); | ||
} | ||
if( alg !== 'RS256' ) { | ||
this.algorithm = alg; | ||
this.key = jwkToPem( jwk ); | ||
} | ||
else { | ||
throw new Error( 'Unsupported algorithm in JKS: ' + alg ); | ||
} | ||
const algorithm = options.algorithm || process.env.VANDIUM_JWT_ALGORITHM; | ||
if( use !== 'sig' ) { | ||
if( (options.enabled === false) || !algorithm ) { | ||
throw new Error( 'Key is not set to signature use' ); | ||
} | ||
this.enabled = false; | ||
return; | ||
} | ||
this.algorithm = alg; | ||
this.key = jwkToPem( jwk ); | ||
} | ||
else { | ||
this.algorithm = resolveAlgorithm( algorithm ); | ||
const algorithm = options.algorithm; | ||
if( this.algorithm === 'RS256' ) { | ||
if( options.enabled === false ) { | ||
const key = requiredOption( options, 'publicKey', options.key, | ||
process.env.VANDIUM_JWT_PUBKEY, process.env.VANDIUM_JWT_KEY ); | ||
this.enabled = false; | ||
return; | ||
} | ||
this.key = jwt.formatPublicKey( key ); | ||
} | ||
else { | ||
this.algorithm = resolveAlgorithm( algorithm ); | ||
this.key = requiredOption( options, 'secret', options.key, | ||
process.env.VANDIUM_JWT_SECRET, process.env.VANDIUM_JWT_KEY ); | ||
} | ||
} | ||
if( this.algorithm === 'RS256' ) { | ||
this.xsrf = utils.parseBoolean( optionValue( options, 'xsrf', process.env.VANDIUM_JWT_USE_XSRF, false ) ); | ||
const key = requiredOption( options, 'publicKey', options.key ); | ||
if( this.xsrf ) { | ||
this.key = formatPublicKey( key ); | ||
} | ||
else { | ||
this.xsrfTokenPath = optionValue( options, 'xsrfToken', process.env.VANDIUM_JWT_XSRF_TOKEN_PATH, | ||
DEFAULT_XSRF_TOKEN_PATH ).split( '.' ); | ||
this.xsrfClaimPath = optionValue( options, 'xsrfClaim', | ||
process.env.VANDIUM_JWT_XSRF_CLAIM_PATH, DEFAULT_XSRF_CLAIM_PATH ).split( '.' ); | ||
} | ||
this.key = requiredOption( options, 'secret', options.key ); | ||
} | ||
} | ||
this.tokenPath = optionValue( options, 'token', process.env.VANDIUM_JWT_TOKEN_PATH, DEFAULT_JWT_TOKEN_PATH ).split( '.' ); | ||
this.xsrf = parseBoolean( optionValue( options, 'xsrf', false ) ); | ||
this.enabled = true; | ||
if( this.xsrf ) { | ||
this.xsrfTokenPath = optionValue( options, 'xsrfToken', DEFAULT_XSRF_TOKEN_PATH ).split( '.' ); | ||
this.xsrfClaimPath = optionValue( options, 'xsrfClaim', DEFAULT_XSRF_CLAIM_PATH ).split( '.' ); | ||
} | ||
validate( event ) { | ||
this.tokenPath = optionValue( options, 'token', DEFAULT_JWT_TOKEN_PATH ).split( '.' ); | ||
if( !this.enabled ) { | ||
this.enabled = true; | ||
} | ||
// nothing to validate | ||
return; | ||
} | ||
validate( event ) { | ||
let token = utils.valueFromPath( event, this.tokenPath ); | ||
if( !this.enabled ) { | ||
// Authorization Bearer | ||
if( token && token.startsWith( 'Bearer' ) ) { | ||
// nothing to validate | ||
return; | ||
} | ||
token = token.replace( 'Bearer', '' ).trim(); | ||
} | ||
let token = valueFromPath( event, this.tokenPath ); | ||
let decoded = jwt.decode( token, this.algorithm, this.key ); | ||
// Authorization Bearer | ||
if( token && token.startsWith( 'Bearer' ) ) { | ||
if( this.xsrf ) { | ||
token = token.replace( 'Bearer', '' ).trim(); | ||
} | ||
let xsrfToken = utils.valueFromPath( event, this.xsrfTokenPath ); | ||
let decoded = decode( token, this.algorithm, this.key ); | ||
jwt.validateXSRF( decoded, xsrfToken, this.xsrfClaimPath ); | ||
} | ||
if( this.xsrf ) { | ||
event.jwt = decoded; | ||
let xsrfToken = valueFromPath( event, this.xsrfTokenPath ); | ||
validateXSRF( decoded, xsrfToken, this.xsrfClaimPath ); | ||
} | ||
event.jwt = decoded; | ||
} | ||
} | ||
module.exports = JWTValidator; |
@@ -5,5 +5,5 @@ const Handler = require( './handler' ); | ||
constructor( options = {} ) { | ||
constructor() { | ||
super( options ); | ||
super(); | ||
} | ||
@@ -19,8 +19,14 @@ | ||
function createHandler( options ) { | ||
function createHandler( handler, options ) { | ||
return new CustomHandler( options ) | ||
.createLambda(); | ||
const instance = new CustomHandler( options ); | ||
if( handler ) { | ||
instance.handler( handler ); | ||
} | ||
return instance.createLambda(); | ||
} | ||
module.exports = createHandler; |
@@ -9,21 +9,21 @@ const utils = require( '../utils' ); | ||
let promise; | ||
let promise; | ||
try { | ||
try { | ||
if( func.length <= 1 ) { | ||
if( func.length <= 1 ) { | ||
promise = Promise.resolve( func( handlerContext ) ); | ||
} | ||
else { | ||
promise = utils.asPromise( func, handlerContext ); | ||
} | ||
promise = Promise.resolve( func( handlerContext ) ); | ||
} | ||
catch( err ) { | ||
else { | ||
promise = Promise.reject( err ); | ||
promise = utils.asPromise( func, handlerContext ); | ||
} | ||
} | ||
catch( err ) { | ||
return promise; | ||
promise = Promise.reject( err ); | ||
} | ||
return promise; | ||
} | ||
@@ -33,7 +33,7 @@ | ||
if( safeContext.callbackWaitsForEmptyEventLoop === false ) { | ||
if( safeContext.callbackWaitsForEmptyEventLoop === false ) { | ||
// let lambda context know that we don't want to wait for the empty event loop | ||
context.callbackWaitsForEmptyEventLoop = false; | ||
} | ||
// let lambda context know that we don't want to wait for the empty event loop | ||
context.callbackWaitsForEmptyEventLoop = false; | ||
} | ||
} | ||
@@ -43,10 +43,10 @@ | ||
const safe = { | ||
const safe = { | ||
...context, | ||
getRemainingTimeInMillis: context.getRemainingTimeInMillis, | ||
event, | ||
}; | ||
...context, | ||
getRemainingTimeInMillis: context.getRemainingTimeInMillis, | ||
event, | ||
}; | ||
return safe; | ||
return safe; | ||
} | ||
@@ -56,183 +56,185 @@ | ||
constructor() { | ||
constructor() { | ||
this._configuration = {}; | ||
this._configuration = {}; | ||
this.afterFunc = () => {}; | ||
this.afterFunc = () => {}; | ||
this._execPipeline = this._createPipeline(); | ||
this._execPipeline = this._createPipeline(); | ||
this.eventProcessor( (event) => event ); | ||
} | ||
this.eventProcessor( (event) => event ); | ||
} | ||
addMethodsToHandler( lambdaHandler ) { | ||
addMethodsToHandler( lambdaHandler ) { | ||
this.addlambdaHandlerMethod( 'before', lambdaHandler ); | ||
this.addlambdaHandlerMethod( 'callbackWaitsForEmptyEventLoop', lambdaHandler ); | ||
this.addlambdaHandlerMethod( 'finally', lambdaHandler ); | ||
} | ||
this.addlambdaHandlerMethod( 'before', lambdaHandler ); | ||
this.addlambdaHandlerMethod( 'callbackWaitsForEmptyEventLoop', lambdaHandler ); | ||
this.addlambdaHandlerMethod( 'finally', lambdaHandler ); | ||
} | ||
addlambdaHandlerMethod( methodName, lambdaHandler ) { | ||
addlambdaHandlerMethod( methodName, lambdaHandler ) { | ||
lambdaHandler[ methodName ] = ( ...args ) => { | ||
lambdaHandler[ methodName ] = ( ...args ) => { | ||
this[ methodName ]( ...args ); | ||
return lambdaHandler; | ||
} | ||
this[ methodName ]( ...args ); | ||
return lambdaHandler; | ||
} | ||
} | ||
handler( handlerFunc ) { | ||
handler( handlerFunc ) { | ||
this.executor = executors.create( handlerFunc ); | ||
this.executor = executors.create( handlerFunc ); | ||
return this; | ||
} | ||
return this; | ||
} | ||
executePreprocessors( state ) { | ||
executePreprocessors( state ) { | ||
if( this._configuration.callbackWaitsForEmptyEventLoop === false ) { | ||
if( this._configuration.callbackWaitsForEmptyEventLoop === false ) { | ||
state.context.callbackWaitsForEmptyEventLoop = false; | ||
} | ||
state.context.callbackWaitsForEmptyEventLoop = false; | ||
} | ||
} | ||
async processResult( result /*, context*/ ) { | ||
async processResult( result /*, context*/ ) { | ||
return { result }; | ||
} | ||
return { result }; | ||
} | ||
async processError( error /*, context*/ ) { | ||
async processError( error /*, context*/ ) { | ||
return { error }; | ||
} | ||
return { error }; | ||
} | ||
async execute( event, context ) { | ||
async execute( event, context, hooks ) { | ||
const safeContext = makeSafeContext( event, context ); | ||
const safeContext = makeSafeContext( event, context ); | ||
try { | ||
try { | ||
const { error, result } = await this._executePipeline( event, safeContext ); | ||
const { error, result } = await this._executePipeline( event, safeContext, hooks ); | ||
if( error ) { | ||
if( error ) { | ||
throw error | ||
} | ||
throw error | ||
} | ||
return result; | ||
} | ||
finally { | ||
return result; | ||
} | ||
finally { | ||
updateContext( context, safeContext ); | ||
} | ||
updateContext( context, safeContext ); | ||
} | ||
} | ||
async _executePipeline( event, context ) { | ||
async _executePipeline( event, context, hooks ) { | ||
let state = { | ||
const state = { | ||
event: utils.clone( event ), | ||
context, | ||
executor: this.executor, | ||
extra: {}, | ||
}; | ||
event: utils.clone( event ), | ||
context, | ||
executor: this.executor, | ||
extra: {}, | ||
hooks, | ||
}; | ||
const pipeline = this._execPipeline.executor(); | ||
const pipeline = this._execPipeline.executor(); | ||
try { | ||
try { | ||
const result = await pipeline.run( state ); | ||
const result = await pipeline.run( state ); | ||
return await this.processResult( result, state.context, state.extra ); | ||
} | ||
catch( err ) { | ||
return await this.processResult( result, state.context, state.extra ); | ||
} | ||
catch( err ) { | ||
return await this.processError( err, state.context, state.extra ); | ||
} | ||
finally { | ||
return await this.processError( err, state.context, state.extra ); | ||
} | ||
finally { | ||
if( pipeline.wasStageRun( 'beforeHandler' ) ) { | ||
if( pipeline.wasStageRun( 'beforeHandler' ) ) { | ||
try { | ||
try { | ||
await asPromise( this.afterFunc, state.context ); | ||
} | ||
catch( err ) { | ||
await asPromise( this.afterFunc, state.context ); | ||
} | ||
catch( err ) { | ||
console.log( 'uncaught exception during finally:', err ); | ||
} | ||
} | ||
console.log( 'uncaught exception during finally:', err ); | ||
} | ||
} | ||
} | ||
} | ||
before( beforeFunc ) { | ||
before( beforeFunc ) { | ||
this._execPipeline.stage( 'beforeHandler', async (state) => { | ||
this._execPipeline.stage( 'beforeHandler', async (state) => { | ||
const { context } = state; | ||
const { context } = state; | ||
context.additional = await asPromise( beforeFunc, context ); | ||
}); | ||
context.additional = await asPromise( beforeFunc, context ); | ||
}); | ||
return this; | ||
} | ||
return this; | ||
} | ||
callbackWaitsForEmptyEventLoop( enabled = true) { | ||
callbackWaitsForEmptyEventLoop( enabled = true) { | ||
this._configuration.callbackWaitsForEmptyEventLoop = enabled; | ||
return this; | ||
} | ||
this._configuration.callbackWaitsForEmptyEventLoop = enabled; | ||
return this; | ||
} | ||
finally( afterFunc ) { | ||
finally( afterFunc ) { | ||
this.afterFunc = afterFunc; | ||
return this; | ||
} | ||
this.afterFunc = afterFunc; | ||
return this; | ||
} | ||
eventProcessor( eventProc ) { | ||
eventProcessor( eventProc ) { | ||
this._execPipeline.stage( 'runHandler', async (state) => { | ||
this._execPipeline.stage( 'runHandler', async (state) => { | ||
const { event, context, executor } = state; | ||
const { event, context, executor } = state; | ||
return await executor( eventProc( event ), context ); | ||
}); | ||
return await executor( eventProc( event ), context ); | ||
}); | ||
return this; | ||
} | ||
return this; | ||
} | ||
createLambda() { | ||
createLambda() { | ||
let lambdaHandler = async ( event, context ) => { | ||
const lambdaHandler = async ( event, context ) => { | ||
return this.execute( event, context ); | ||
}; | ||
return this.execute( event, context ); | ||
}; | ||
this.addMethodsToHandler( lambdaHandler ); | ||
this.addMethodsToHandler( lambdaHandler ); | ||
return lambdaHandler; | ||
} | ||
lambdaHandler.execute = async ( ...params ) => this.execute( ...params ); | ||
_createPipeline() { | ||
return lambdaHandler; | ||
} | ||
let pipeline = new Pipeline( [ 'preprocessors', 'validateExecutor', 'beforeHandler', 'runHandler' ] ); | ||
_createPipeline() { | ||
const pipeline = new Pipeline( [ 'preprocessors', 'validateExecutor', 'beforeHandler', 'runHandler' ] ); | ||
pipeline.stage( 'preprocessors', (state) => { | ||
pipeline.stage( 'preprocessors', (state) => { | ||
this.executePreprocessors( state ); | ||
}); | ||
this.executePreprocessors( state ); | ||
}); | ||
pipeline.stage( 'validateExecutor', (state) => { | ||
pipeline.stage( 'validateExecutor', (state) => { | ||
if( !state.executor ) { | ||
if( !state.executor ) { | ||
throw new Error( 'handler not defined' ); | ||
} | ||
}); | ||
throw new Error( 'handler not defined' ); | ||
} | ||
}); | ||
return pipeline; | ||
} | ||
return pipeline; | ||
} | ||
} | ||
module.exports = Handler; |
@@ -11,10 +11,10 @@ 'use strict'; | ||
module.exports = {}; | ||
const { api, apigateway } = require( './api' ); | ||
// specialized | ||
module.exports.api = require( './api' ); | ||
module.exports.generic = require( './custom' ); | ||
const generic = require( './custom' ); | ||
function asEventInfo( obj ) { | ||
function getTypes() { | ||
const asEventInfo = ( obj ) => { | ||
if( isObject( obj ) ) { | ||
@@ -27,6 +27,13 @@ | ||
return { name: obj, type: obj }; | ||
} | ||
} | ||
// record types | ||
[ | ||
const eventTypes = { | ||
// specialized | ||
api, | ||
generic, | ||
}; | ||
// record types | ||
[ | ||
's3', | ||
@@ -41,11 +48,11 @@ 'dynamodb', | ||
].forEach( ( obj ) => { | ||
].forEach( ( obj ) => { | ||
const { name, type } = asEventInfo( obj ); | ||
module.exports[ name ] = ( ...args ) => record( type, ...args ); | ||
}); | ||
eventTypes[ name ] = ( ...args ) => record( type, ...args ); | ||
}); | ||
// simple event | ||
[ | ||
// simple event | ||
[ | ||
'cloudformation', | ||
@@ -59,3 +66,3 @@ { name: 'logs', type: 'cloudwatch' }, | ||
].forEach( ( obj ) => { | ||
].forEach( ( obj ) => { | ||
@@ -66,8 +73,22 @@ const { name, type } = asEventInfo( obj ); | ||
module.exports[ name ] = ( ...args ) => cloudwatch( name, ...args ); | ||
eventTypes[ name ] = ( ...args ) => cloudwatch( name, ...args ); | ||
} | ||
else { | ||
module.exports[ name ] = ( ...args ) => basic( type, ...args ); | ||
eventTypes[ name ] = ( ...args ) => basic( type, ...args ); | ||
} | ||
}); | ||
}); | ||
const handlerTypes = { | ||
apigateway, | ||
...eventTypes, | ||
}; | ||
return { handlerTypes, eventTypes }; | ||
} | ||
module.exports = { | ||
...getTypes() | ||
}; |
@@ -0,1 +1,2 @@ | ||
class Pipeline { | ||
@@ -5,5 +6,5 @@ | ||
this.executionOrder = [ ...stageOrder ]; | ||
this.handlers = new Map(); | ||
this.handlers = {}; | ||
stageOrder.forEach( (stage) => this.handlers.set( stage, () => {} ) ); | ||
} | ||
@@ -13,9 +14,9 @@ | ||
if( !this.executionOrder.includes( stageName ) ) { | ||
if( !this.handlers.has( stageName ) ) { | ||
throw new Error( `Invalid stage: ${stageName}` ); | ||
} | ||
throw new Error( `Invalid stage: ${stageName}` ); | ||
} | ||
this.handlers[ stageName ] = handler; | ||
return this; | ||
this.handlers.set( stageName, handler ); | ||
return this; | ||
} | ||
@@ -25,3 +26,3 @@ | ||
return new PipelineExecutorAsync( this ); | ||
return new PipelineExecutorAsync( Array.from( this.handlers.entries() ) ); | ||
} | ||
@@ -31,42 +32,48 @@ | ||
return new PipelineExecutorSync( this ); | ||
return new PipelineExecutorSync( Array.from( this.handlers.entries() ) ); | ||
} | ||
} | ||
const noOpenHook = () => {}; | ||
class PipelineExecutorBase { | ||
constructor( pipeline ) { | ||
constructor( handlers ) { | ||
this.executionOrder = [ ...pipeline.executionOrder ]; | ||
this.handlers = { ...pipeline.handlers }; | ||
this.handlers = handlers; | ||
this._resetExecState(); | ||
} | ||
this._resetExecState(); | ||
} | ||
wasStageRun( stageName ) { | ||
wasStageRun( stageName ) { | ||
const { last } = this.execState; | ||
const { stagesRun } = this.execState; | ||
if( last ) { | ||
return stagesRun.includes( stageName ); | ||
} | ||
const lastIndex = this.executionOrder.indexOf( last ); | ||
const stageIndex = this.executionOrder.indexOf( stageName ); | ||
_resetExecState() { | ||
if( lastIndex > -1 && stageIndex > -1 ) { | ||
this.execState = { | ||
return (lastIndex >= stageIndex); | ||
} | ||
} | ||
last: null, | ||
current: null, | ||
stagesRun: [], | ||
}; | ||
} | ||
} | ||
return false; | ||
} | ||
function getHandlerExecs( stage, handler, hooks ) { | ||
_resetExecState() { | ||
const methodHook = hooks[ stage ] || {}; | ||
this.execState = { | ||
const { | ||
last: null, | ||
current: null, | ||
}; | ||
} | ||
before = noOpenHook, | ||
stub : handlerExec = handler, | ||
after = noOpenHook, | ||
} = methodHook; | ||
return [ before, handlerExec, after ]; | ||
} | ||
@@ -76,29 +83,32 @@ | ||
constructor( pipeline ) { | ||
constructor( pipeline ) { | ||
super( pipeline ); | ||
} | ||
super( pipeline ); | ||
} | ||
async run( state ) { | ||
async run( state ) { | ||
this._resetExecState(); | ||
this._resetExecState(); | ||
let lastResult; | ||
const { hooks = {} } = state; | ||
for( let method of this.executionOrder ) { | ||
let lastResult; | ||
const handler = this.handlers[method]; | ||
for( let [stage,stageHandler] of this.handlers ) { | ||
if( handler ) { | ||
this.execState.current = stage; | ||
this.execState.current = method; | ||
const [ beforeHandler, handler, afterHandler ] = getHandlerExecs( stage, stageHandler, hooks ); | ||
lastResult = await handler( state ); | ||
beforeHandler( state, stage ); | ||
this.execState.last = method; | ||
} | ||
} | ||
lastResult = await handler( state ); | ||
return lastResult; | ||
afterHandler( state, stage ); | ||
this.execState.stagesRun.push( stage ); | ||
} | ||
return lastResult; | ||
} | ||
} | ||
@@ -117,16 +127,19 @@ | ||
const { hooks = {} } = state; | ||
let lastResult; | ||
for( let method of this.executionOrder ) { | ||
for( let [stage,stageHandler] of this.handlers ) { | ||
const handler = this.handlers[method]; | ||
this.execState.current = stage; | ||
if( handler ) { | ||
const [ beforeHandler, handler, afterHandler ] = getHandlerExecs( stage, stageHandler, hooks ); | ||
this.execState.current = method; | ||
beforeHandler( state, stage ); | ||
lastResult = handler( state ); | ||
lastResult = handler( state ); | ||
this.execState.last = method; | ||
} | ||
afterHandler( state, stage ); | ||
this.execState.stagesRun.push( stage ); | ||
} | ||
@@ -133,0 +146,0 @@ |
@@ -12,17 +12,17 @@ // load environment variables from SSM (if configured) | ||
const eventTypes = require( './event_types' ); | ||
const { eventTypes } = require( './event_types' ); | ||
const createHandler = require( './create-handler' ); | ||
const { useJwks } = require( './auth' ); | ||
const vandium = { | ||
types: validation.types | ||
types: validation.types, | ||
...eventTypes, | ||
useJwks, | ||
createHandler, | ||
}; | ||
////////////////// | ||
// event types | ||
// /////////////// | ||
for( let type in eventTypes ) { | ||
vandium[ type ] = eventTypes[ type ]; | ||
} | ||
module.exports = Object.freeze( vandium ); |
{ | ||
"name": "vandium", | ||
"version": "6.1.0", | ||
"version": "7.0.0-Beta1", | ||
"description": "AWS Lambda framework for building functions using Node.js for API Gateway, IoT applications, and other AWS events", | ||
@@ -34,3 +34,3 @@ "main": "lib/index.js", | ||
"scripts": { | ||
"test": "nyc mocha '**/__tests__/*' --recursive", | ||
"test": "nyc mocha 'lib/**/__tests__/*' --recursive", | ||
"lint": "eslint" | ||
@@ -60,2 +60,3 @@ }, | ||
"jwk-to-pem": "^2.0.3", | ||
"jwks-source": "^1.0.0", | ||
"jwt-simple": "^0.5.6", | ||
@@ -67,3 +68,3 @@ "qs": "^6.5.2", | ||
"app-root-path": "^3.0.0", | ||
"aws-sdk": "^2.618.0", | ||
"aws-sdk": "^2.620.0", | ||
"chai": "^4.1.2", | ||
@@ -70,0 +71,0 @@ "dotenv": "^8.2.0", |
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 1 instance in 1 package
No v1
QualityPackage is not semver >=1. This means it is not stable and does not support ^ ranges.
Found 1 instance in 1 package
72943
48
1815
10
2
16
+ Addedjwks-source@^1.0.0
+ Addedget-port@3.2.0(transitive)
+ Addedjwks-source@1.0.0(transitive)
+ Addedsync-rpc@1.3.6(transitive)