UNPKG

@bitblit/epsilon

Version:

Tiny adapter to simplify building API gateway Lambda APIS

282 lines 14.6 kB
"use strict"; var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; Object.defineProperty(exports, "__esModule", { value: true }); exports.BuiltInFilters = void 0; const logger_1 = require("@bitblit/ratchet/common/logger"); const string_ratchet_1 = require("@bitblit/ratchet/common/string-ratchet"); const map_ratchet_1 = require("@bitblit/ratchet/common/map-ratchet"); const event_util_1 = require("../../http/event-util"); const bad_request_error_1 = require("../../http/error/bad-request-error"); const response_util_1 = require("../../http/response-util"); const misconfigured_error_1 = require("../../http/error/misconfigured-error"); const common_1 = require("@bitblit/ratchet/common"); class BuiltInFilters { static combineFilters(fCtx, filters) { return __awaiter(this, void 0, void 0, function* () { let cont = true; if (filters && filters.length > 0) { for (let i = 0; i < filters.length && cont; i++) { cont = yield filters[i](fCtx); } } return cont; }); } static applyGzipIfPossible(fCtx) { var _a; return __awaiter(this, void 0, void 0, function* () { if (((_a = fCtx.event) === null || _a === void 0 ? void 0 : _a.headers) && fCtx.result) { const encodingHeader = fCtx.event && fCtx.event.headers ? map_ratchet_1.MapRatchet.extractValueFromMapIgnoreCase(fCtx.event.headers, 'accept-encoding') : null; fCtx.result = yield response_util_1.ResponseUtil.applyGzipIfPossible(encodingHeader, fCtx.result); } return true; }); } static addConstantHeaders(fCtx, headers) { return __awaiter(this, void 0, void 0, function* () { if (headers && fCtx.result) { fCtx.result.headers = Object.assign({}, headers, fCtx.result.headers); } else { logger_1.Logger.warn('Could not add headers - either result or headers were missing'); } return true; }); } static addAWSRequestIdHeader(fCtx, headerName = 'X-REQUEST-ID') { var _a; return __awaiter(this, void 0, void 0, function* () { if (fCtx.result && string_ratchet_1.StringRatchet.trimToNull(headerName) && headerName.startsWith('X-')) { fCtx.result.headers = fCtx.result.headers || {}; fCtx.result.headers[headerName] = ((_a = fCtx.context) === null || _a === void 0 ? void 0 : _a.awsRequestId) || 'Request-Id-Missing'; } else { logger_1.Logger.warn('Could not add request id header - either result or context were missing or name was invalid'); } return true; }); } static addAllowEverythingCORSHeaders(fCtx) { return __awaiter(this, void 0, void 0, function* () { return BuiltInFilters.addConstantHeaders(fCtx, { 'Access-Control-Allow-Origin': '*', 'Access-Control-Allow-Methods': '*', 'Access-Control-Allow-Headers': '*', }); }); } static addAllowReflectionCORSHeaders(fCtx) { return __awaiter(this, void 0, void 0, function* () { return BuiltInFilters.addConstantHeaders(fCtx, { 'Access-Control-Allow-Origin': map_ratchet_1.MapRatchet.caseInsensitiveAccess(fCtx.event.headers, 'Origin') || '*', 'Access-Control-Allow-Methods': map_ratchet_1.MapRatchet.caseInsensitiveAccess(fCtx.event.headers, 'Access-Control-Request-Method') || '*', 'Access-Control-Allow-Headers': map_ratchet_1.MapRatchet.caseInsensitiveAccess(fCtx.event.headers, 'Access-Control-Request-Headers') || '*', }); }); } static uriDecodeQueryParams(fCtx) { var _a, _b; return __awaiter(this, void 0, void 0, function* () { if ((_a = fCtx === null || fCtx === void 0 ? void 0 : fCtx.event) === null || _a === void 0 ? void 0 : _a.queryStringParameters) { Object.keys(fCtx.event.queryStringParameters).forEach((k) => { const val = fCtx.event.queryStringParameters[k]; if (val) { fCtx.event.queryStringParameters[k] = BuiltInFilters.decodeUriComponentAndReplacePlus(val); } }); } if ((_b = fCtx === null || fCtx === void 0 ? void 0 : fCtx.event) === null || _b === void 0 ? void 0 : _b.multiValueQueryStringParameters) { Object.keys(fCtx.event.multiValueQueryStringParameters).forEach((k) => { const val = fCtx.event.multiValueQueryStringParameters[k]; if (val && val.length) { const cleaned = val.map((v) => BuiltInFilters.decodeUriComponentAndReplacePlus(v)); fCtx.event.multiValueQueryStringParameters[k] = cleaned; } }); } return true; }); } /** * Performs decodeURIComponent on a value after replacing all "+" values with spaces. */ static decodeUriComponentAndReplacePlus(val) { return decodeURIComponent(val.replace(/\+/g, ' ')); } static fixStillEncodedQueryParams(fCtx) { return __awaiter(this, void 0, void 0, function* () { event_util_1.EventUtil.fixStillEncodedQueryParams(fCtx.event); return true; }); } static disallowStringNullAsPathParameter(fCtx) { var _a; return __awaiter(this, void 0, void 0, function* () { if ((_a = fCtx === null || fCtx === void 0 ? void 0 : fCtx.event) === null || _a === void 0 ? void 0 : _a.pathParameters) { Object.keys(fCtx.event.pathParameters).forEach((k) => { if ('null' === string_ratchet_1.StringRatchet.trimToEmpty(fCtx.event.pathParameters[k]).toLowerCase()) { throw new bad_request_error_1.BadRequestError().withFormattedErrorMessage('Path parameter %s was string -null-', k); } }); } return true; }); } static disallowStringNullAsQueryStringParameter(fCtx) { var _a; return __awaiter(this, void 0, void 0, function* () { if ((_a = fCtx === null || fCtx === void 0 ? void 0 : fCtx.event) === null || _a === void 0 ? void 0 : _a.queryStringParameters) { Object.keys(fCtx.event.queryStringParameters).forEach((k) => { if ('null' === string_ratchet_1.StringRatchet.trimToEmpty(fCtx.event.queryStringParameters[k]).toLowerCase()) { throw new bad_request_error_1.BadRequestError().withFormattedErrorMessage('Query parameter %s was string -null-', k); } }); } return true; }); } static ensureEventMaps(fCtx) { return __awaiter(this, void 0, void 0, function* () { fCtx.event.queryStringParameters = fCtx.event.queryStringParameters || {}; fCtx.event.headers = fCtx.event.headers || {}; fCtx.event.pathParameters = fCtx.event.pathParameters || {}; return true; }); } static parseJsonBodyToObject(fCtx) { var _a; return __awaiter(this, void 0, void 0, function* () { if ((_a = fCtx.event) === null || _a === void 0 ? void 0 : _a.body) { try { fCtx.event.parsedBody = event_util_1.EventUtil.jsonBodyToObject(fCtx.event); } catch (err) { throw new common_1.RestfulApiHttpError('Supplied body was not parsable as valid JSON').withHttpStatusCode(400); } } return true; }); } static checkMaximumLambdaBodySize(fCtx) { var _a; return __awaiter(this, void 0, void 0, function* () { if (((_a = fCtx.result) === null || _a === void 0 ? void 0 : _a.body) && fCtx.result.body.length > BuiltInFilters.MAXIMUM_LAMBDA_BODY_SIZE_BYTES) { const delta = fCtx.result.body.length - BuiltInFilters.MAXIMUM_LAMBDA_BODY_SIZE_BYTES; throw new common_1.RestfulApiHttpError('Response size is ' + fCtx.result.body.length + ' bytes, which is ' + delta + ' bytes too large for this handler').withHttpStatusCode(500); } return true; }); } static validateInboundBody(fCtx) { var _a; return __awaiter(this, void 0, void 0, function* () { if (((_a = fCtx === null || fCtx === void 0 ? void 0 : fCtx.event) === null || _a === void 0 ? void 0 : _a.parsedBody) && fCtx.routeAndParse) { if (fCtx.routeAndParse.mapping.validation) { if (!fCtx.modelValidator) { throw new misconfigured_error_1.MisconfiguredError('Requested body validation but supplied no validator'); } const errors = fCtx.modelValidator.validate(fCtx.routeAndParse.mapping.validation.modelName, fCtx.event.parsedBody, fCtx.routeAndParse.mapping.validation.emptyAllowed, fCtx.routeAndParse.mapping.validation.extraPropertiesAllowed); if (errors.length > 0) { logger_1.Logger.info('Found errors while validating %s object %j', fCtx.routeAndParse.mapping.validation.modelName, errors); const newError = new bad_request_error_1.BadRequestError(...errors); throw newError; } } } else { logger_1.Logger.debug('No validation since no route specified or no parsed body'); } return true; }); } static validateInboundQueryParams(fCtx) { return __awaiter(this, void 0, void 0, function* () { // TODO: Implement ME! return true; }); } static validateInboundPathParams(fCtx) { return __awaiter(this, void 0, void 0, function* () { // TODO: Implement ME! return true; }); } static validateOutboundResponse(fCtx) { return __awaiter(this, void 0, void 0, function* () { // Use !== true below because commonly it just wont be spec'd if (fCtx === null || fCtx === void 0 ? void 0 : fCtx.rawResult) { if (fCtx.routeAndParse.mapping.outboundValidation) { logger_1.Logger.debug('Applying outbound check to %j', fCtx.rawResult); const errors = fCtx.modelValidator.validate(fCtx.routeAndParse.mapping.outboundValidation.modelName, fCtx.rawResult, fCtx.routeAndParse.mapping.outboundValidation.emptyAllowed, fCtx.routeAndParse.mapping.outboundValidation.extraPropertiesAllowed); if (errors.length > 0) { logger_1.Logger.error('Found outbound errors while validating %s object %j', fCtx.routeAndParse.mapping.outboundValidation.modelName, errors); errors.unshift('Server sent object invalid according to spec'); throw new common_1.RestfulApiHttpError().withErrors(errors).withHttpStatusCode(500).withDetails(fCtx.rawResult); } } else { logger_1.Logger.debug('Applied no outbound validation because none set'); } } else { logger_1.Logger.debug('No validation since no outbound body or disabled'); } return true; }); } static autoRespondToOptionsRequestWithCors(fCtx) { var _a; return __awaiter(this, void 0, void 0, function* () { if (string_ratchet_1.StringRatchet.trimToEmpty((_a = fCtx === null || fCtx === void 0 ? void 0 : fCtx.event) === null || _a === void 0 ? void 0 : _a.httpMethod).toLowerCase() === 'options') { fCtx.result = { statusCode: 200, body: '{"cors":true, "m":2}', }; yield BuiltInFilters.addAllowReflectionCORSHeaders(fCtx); return false; } else { return true; } }); } static autoRespond(fCtx, inBody) { return __awaiter(this, void 0, void 0, function* () { const body = inBody || { message: 'Not Implemented', }; fCtx.result = { statusCode: 200, body: JSON.stringify(body), }; return false; }); } static secureOutboundServerErrorForProduction(fCtx, errorMessage, errCode) { var _a; return __awaiter(this, void 0, void 0, function* () { if ((_a = fCtx === null || fCtx === void 0 ? void 0 : fCtx.result) === null || _a === void 0 ? void 0 : _a.statusCode) { if (errCode === null || fCtx.result.statusCode === errCode) { logger_1.Logger.warn('Securing outbound error info (was : %j)', fCtx.result.body); fCtx.rawResult = new common_1.RestfulApiHttpError(errorMessage).withHttpStatusCode(fCtx.result.statusCode); const oldResult = fCtx.result; fCtx.result = response_util_1.ResponseUtil.errorResponse(fCtx.rawResult); // Need this to preserve any CORS headers, etc fCtx.result.headers = Object.assign({}, oldResult.headers || {}, fCtx.result.headers || {}); } } return true; }); } } exports.BuiltInFilters = BuiltInFilters; BuiltInFilters.MAXIMUM_LAMBDA_BODY_SIZE_BYTES = 1024 * 1024 * 5 - 1024 * 100; // 5Mb - 100k buffer //# sourceMappingURL=built-in-filters.js.map